From 53ef4b62a75228edb332ed69df0123246d9a3907 Mon Sep 17 00:00:00 2001 From: spark33 Date: Mon, 23 Oct 2023 15:37:29 -0400 Subject: [PATCH 01/10] private content pages --- components/navigation/NavigationContent.tsx | 101 ++++++++++++++---- .../[contentPageGroup]/[contentPageTitle].tsx | 2 + .../private/[contentPageTitle].tsx | 41 +++++++ .../ContentStack/getContentstackResources.ts | 19 +++- utils/ContentStack/types.ts | 3 +- 5 files changed, 141 insertions(+), 25 deletions(-) create mode 100644 pages/[contentPageGroup]/private/[contentPageTitle].tsx diff --git a/components/navigation/NavigationContent.tsx b/components/navigation/NavigationContent.tsx index fd6d002a..f9d3d53e 100644 --- a/components/navigation/NavigationContent.tsx +++ b/components/navigation/NavigationContent.tsx @@ -1,3 +1,4 @@ +import { css } from '@emotion/react'; import styled from '@emotion/styled'; import { useAppContext } from 'contexts/AppContext'; import kebabCase from 'lodash/kebabCase'; @@ -49,15 +50,45 @@ function NavigationContent({ key={contentPageGroup.uid} header={contentPageGroup.title} > - {contentPageGroup.content_pages.map(({ title }) => ( - router.push(`/foundation/${title}`)} - active={title === activePage} - > - {title.split('-').join(' ')} - - ))} + {contentPageGroup.content_pages.map(({ title, is_private }) => { + const shouldRenderAsLocked = is_private && !session; + return ( + + router.push( + `/${contentPageGroup.title}/${ + is_private ? 'private/' : '' + }${title}`, + ) + } + active={title === activePage} + > +
+
+ {title.split('-').join(' ')} + {shouldRenderAsLocked && ( + + + + )} +
+
+ {shouldRenderAsLocked && ( + + Log in to view this page + + )} +
+
+
+ ); + })} ))} { const contentPageKebabCaseName = kebabCase(contentPage.title); + const shouldRenderAsLocked = + contentPage.is_private && !session; - return ( - - {contentPage.title} - - ); + if (shouldRenderAsLocked) { + return ( + + {contentPage.title} + + + + + } + > + Log in to view this page + + ); + } else { + return ( + + {contentPage.title} + + ); + } })} ))} diff --git a/pages/[contentPageGroup]/[contentPageTitle].tsx b/pages/[contentPageGroup]/[contentPageTitle].tsx index fa62d802..da20400d 100644 --- a/pages/[contentPageGroup]/[contentPageTitle].tsx +++ b/pages/[contentPageGroup]/[contentPageTitle].tsx @@ -2,6 +2,8 @@ import { ReactElement } from 'react'; import ContentPageLayout from 'layouts/ContentPageLayout'; import kebabCase from 'lodash/kebabCase'; import startCase from 'lodash/startCase'; +import { unstable_getServerSession } from 'next-auth'; +import { authOptions } from 'pages/api/auth/[...nextauth]'; import { getContentPage, getContentPageGroups, diff --git a/pages/[contentPageGroup]/private/[contentPageTitle].tsx b/pages/[contentPageGroup]/private/[contentPageTitle].tsx new file mode 100644 index 00000000..d46ee66f --- /dev/null +++ b/pages/[contentPageGroup]/private/[contentPageTitle].tsx @@ -0,0 +1,41 @@ +import { ReactElement } from 'react'; +import ContentPageLayout from 'layouts/ContentPageLayout'; +import startCase from 'lodash/startCase'; +import { unstable_getServerSession } from 'next-auth'; +import { authOptions } from 'pages/api/auth/[...nextauth]'; +import { getContentPage } from 'utils/ContentStack/getContentstackResources'; + +import ContentstackRichText from 'components/ContentstackRichText'; +import Unauthorized from 'components/Unauthorized'; + +const ContentPage = ({ contentPage }) => { + if (contentPage !== null) + return ; + + return ; +}; + +ContentPage.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; + +export async function getServerSideProps({ params, req, res }) { + const session = await unstable_getServerSession(req, res, authOptions); + const contentPage = session + ? await getContentPage(startCase(params.contentPageTitle)) + : null; + return { + props: { + contentPage, + contentPageTitle: contentPage ? contentPage.title : null, + }, + }; +} + +export default ContentPage; diff --git a/utils/ContentStack/getContentstackResources.ts b/utils/ContentStack/getContentstackResources.ts index 7897b0a1..af046abb 100644 --- a/utils/ContentStack/getContentstackResources.ts +++ b/utils/ContentStack/getContentstackResources.ts @@ -107,7 +107,16 @@ export async function getContentPageGroups(): Promise> { const pageGroups: Array = ( await query .includeReference('content_pages') - .only(['content_pages', 'uid', 'title', 'url', 'iconname']) + .only([ + 'content_pages', + 'uid', + 'title', + 'url', + 'iconname', + 'private', + 'rendering_order', + ]) + .ascending('rendering_order') .toJSON() .find() )[0].map(({ content_pages, ...meta }: ContentPageGroup) => { @@ -116,11 +125,15 @@ export async function getContentPageGroups(): Promise> { content_pages: content_pages // TODO: strip fields in initial query // Strip any additional fields - .map(({ uid, title, url }) => ({ uid, title, url })) + .map(({ uid, title, url, is_private }: ContentPage) => ({ + uid, + title, + url, + is_private, + })) .sort((a, b) => a.title.localeCompare(b.title)), }; }); - return pageGroups; } catch (error) { console.error('No Content Page Groups found', error); diff --git a/utils/ContentStack/types.ts b/utils/ContentStack/types.ts index 4fcb50e1..9b3e3e97 100644 --- a/utils/ContentStack/types.ts +++ b/utils/ContentStack/types.ts @@ -3,7 +3,7 @@ export interface ContentPageGroup extends Object { title: string; url: string; iconname: string; - content_pages: Array; + content_pages: Array; } /** @@ -20,6 +20,7 @@ export interface ContentPageMeta { */ export interface ContentPage extends ContentPageMeta { content: unknown; + is_private?: boolean; } /** From 0e63a55201f7c592ae7bb105240fdd1212751b85 Mon Sep 17 00:00:00 2001 From: spark33 Date: Mon, 23 Oct 2023 16:59:16 -0400 Subject: [PATCH 02/10] new component layout init --- layouts/NewComponentLayout.tsx | 76 +++++++++++++++++++ .../component/[componentName]/guidelines.tsx | 5 +- 2 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 layouts/NewComponentLayout.tsx diff --git a/layouts/NewComponentLayout.tsx b/layouts/NewComponentLayout.tsx new file mode 100644 index 00000000..eb8eb08d --- /dev/null +++ b/layouts/NewComponentLayout.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import styled from "@emotion/styled"; +import kebabCase from 'lodash/kebabCase'; +import startCase from 'lodash/startCase'; +import getFullPageTitle from "utils/getFullPageTitle"; +import { mq } from "utils/mediaQuery"; + +import { spacing } from "@leafygreen-ui/tokens"; +import { ComponentFields } from 'utils/ContentStack/types'; +import Head from 'next/head'; +import { Body, H2 } from '@leafygreen-ui/typography'; +import { useSession } from 'next-auth/react'; + +const Main = styled('main')` + display: flex; + justify-content: center; +`; + +const ContentContainer = styled('div')` + ${mq({ + // 51px is a magic number for baseline alignment with the first SideNavGroup header + marginTop: [`${spacing[4]}px`, `${spacing[4]}px`, '51px'], + paddingLeft: [`${spacing[2]}`, `${spacing[6]}px`, `${spacing[7]}px`], + paddingRight: [`${spacing[2]}`, `${spacing[6]}px`, `${spacing[7]}px`], + width: ['100%', '75%', '75%'], + })} +` + + +const Header = styled(H2)` + margin-bottom: ${spacing[4]}px; + text-transform: capitalize; +`; + +const NewComponentLayout = ({ + component, + componentName, + children, +}: { + component?: ComponentFields; + children: React.ReactNode; + componentName: string; +}) => { + const pageTitle = getFullPageTitle(startCase(componentName)); + console.log(component) + const { data: session } = useSession(); + + return ( +
+ + {pageTitle} + + {/* If the description field doesn't exist, it will default to a description of the site, defined in _document. */} + {component?.description && ( + + )} + + + {component && (!component.private || (component.private && session)) ? + +
+ {componentName} +
+ + {component.description} + + {children} +
+ : children} +
+ ) +} + +NewComponentLayout.displayName = 'NewComponentLayout'; + +export default NewComponentLayout; \ No newline at end of file diff --git a/pages/component/[componentName]/guidelines.tsx b/pages/component/[componentName]/guidelines.tsx index 5ef4f693..ea6bb1c8 100644 --- a/pages/component/[componentName]/guidelines.tsx +++ b/pages/component/[componentName]/guidelines.tsx @@ -9,6 +9,7 @@ import isEmptyRichText from 'utils/isEmptyRichText'; import ComingSoon from 'components/ComingSoon'; import ContentstackRichText from 'components/ContentstackRichText'; +import NewComponentLayout from 'layouts/NewComponentLayout'; const ComponentGuidelines = ({ component }) => { const guidelines = component.designguidelines; @@ -23,12 +24,12 @@ const ComponentGuidelines = ({ component }) => { ComponentGuidelines.getLayout = function getLayout(page: ReactElement) { return ( - {page} - + ); }; From 1d81328865835e233a495d44be3099da51968235 Mon Sep 17 00:00:00 2001 From: spark33 Date: Tue, 24 Oct 2023 03:46:54 -0400 Subject: [PATCH 03/10] breadcrumbs and basic responsiveness --- .../ContentstackRichText/ContentstackText.tsx | 13 +- .../ContentstackRichText/HeaderContent.tsx | 20 ++- .../ContentstackRichText/componentMap.tsx | 1 + components/ContentstackRichText/types.ts | 3 +- components/GuidelineBreadcrumbs.tsx | 33 +++++ .../pages/example/KnobsTable/KnobsTable.tsx | 34 +++-- components/pages/example/LiveExample.tsx | 3 +- contexts/GuidelinesContext.tsx | 41 ++++++ layouts/NewComponentLayout.tsx | 123 ++++++++++++------ .../component/[componentName]/guidelines.tsx | 34 ++++- .../ContentStack/getContentstackResources.ts | 5 + 11 files changed, 240 insertions(+), 70 deletions(-) create mode 100644 components/GuidelineBreadcrumbs.tsx create mode 100644 contexts/GuidelinesContext.tsx diff --git a/components/ContentstackRichText/ContentstackText.tsx b/components/ContentstackRichText/ContentstackText.tsx index 250caeb5..34d89bfa 100644 --- a/components/ContentstackRichText/ContentstackText.tsx +++ b/components/ContentstackRichText/ContentstackText.tsx @@ -2,6 +2,7 @@ import { Fragment } from 'react'; import { HTMLElementProps } from '@leafygreen-ui/lib'; import { Polymorph } from '@leafygreen-ui/polymorphic'; +import { InlineCode } from '@leafygreen-ui/typography'; import { CSTextNode } from './types'; @@ -10,7 +11,17 @@ interface CSRichTextProps extends HTMLElementProps<'span'> { } const ContentstackText = ({ node, ...rest }: CSRichTextProps) => { - const renderAs = node.bold ? 'b' : rest.className ? 'span' : Fragment; + let renderAs; + + if (node.bold) { + renderAs = 'b'; + } else if (node.inlineCode) { + renderAs = InlineCode; + } else if (rest.className) { + renderAs = 'span'; + } else { + renderAs = Fragment; + } return ( diff --git a/components/ContentstackRichText/HeaderContent.tsx b/components/ContentstackRichText/HeaderContent.tsx index 8d8a9cd0..3119b87a 100644 --- a/components/ContentstackRichText/HeaderContent.tsx +++ b/components/ContentstackRichText/HeaderContent.tsx @@ -1,4 +1,9 @@ +import { TextNode } from '@contentstack/utils'; import styled from '@emotion/styled'; +import { + BreadcrumbHeader, + useGuidelinesContext, +} from 'contexts/GuidelinesContext'; import { kebabCase } from 'lodash'; import Link from 'next/link'; @@ -6,8 +11,9 @@ import Icon from '@leafygreen-ui/icon'; import { palette } from '@leafygreen-ui/palette'; import ContentstackChildren from './ContentstackChildren'; -import { CSNode } from './types'; +import { AnyNode, CSNode, CSTextNode } from './types'; import { getCSNodeTextContent } from './utils'; +import { useEffect } from 'react'; const StyledAnchor = styled('a')` color: inherit; @@ -53,14 +59,20 @@ const StyledIcon = styled(Icon)` * Content of headers in rich text markup need to be wrapped in links and anchors for hashed links. */ const HeaderContent = ({ node }: { node: CSNode }) => { + const nodeText = getCSNodeTextContent(node); const headerId = kebabCase(getCSNodeTextContent(node)); + const { pushHeader } = useGuidelinesContext(); + + useEffect(() => { + if (node.type === 'h2' || node.type === 'h4') { + pushHeader(node.type as BreadcrumbHeader, nodeText); + } + }, []) return ( - - - + {nodeText} diff --git a/components/ContentstackRichText/componentMap.tsx b/components/ContentstackRichText/componentMap.tsx index 8e42248e..1a2da261 100644 --- a/components/ContentstackRichText/componentMap.tsx +++ b/components/ContentstackRichText/componentMap.tsx @@ -273,6 +273,7 @@ export const nodeTypeToElementMap: { [CSNodeType.REFERENCE]: (node, props) => ( ), + [CSNodeType.CODE]: (node, props) => , [CSNodeType.FRAGMENT]: (node, props) => ( ), diff --git a/components/ContentstackRichText/types.ts b/components/ContentstackRichText/types.ts index 361f478c..1e6ecf68 100644 --- a/components/ContentstackRichText/types.ts +++ b/components/ContentstackRichText/types.ts @@ -4,7 +4,7 @@ import { BadgeProps } from '@leafygreen-ui/badge/dist/Badge/types'; import { ButtonProps } from '@leafygreen-ui/button'; import { CalloutProps } from '@leafygreen-ui/callout/dist/Callout/types'; -type AnyNode = CSNode | CSTextNode; +export type AnyNode = CSNode | CSTextNode; /** Contentstack is missing props in their type definitions */ export interface CSNode extends Node { @@ -26,6 +26,7 @@ export enum CSNodeType { DOCUMENT = 'doc', FRAGMENT = 'fragment', PARAGRAPH = 'p', + CODE = 'code', ANCHOR = 'a', REFERENCE = 'reference', HEADING_1 = 'h1', diff --git a/components/GuidelineBreadcrumbs.tsx b/components/GuidelineBreadcrumbs.tsx new file mode 100644 index 00000000..cb175ce2 --- /dev/null +++ b/components/GuidelineBreadcrumbs.tsx @@ -0,0 +1,33 @@ +import styled from '@emotion/styled'; +import { palette } from '@leafygreen-ui/palette'; +import { Overline } from '@leafygreen-ui/typography'; +import { useGuidelinesContext } from 'contexts/GuidelinesContext'; +import kebabCase from 'lodash/kebabCase'; + +const BreadCrumb = styled('a')` + display: block; +` + +const GuidelineBreadcrumb = ({ children, active = false }) => { + return ( + {children} + ) +} + +const GuidelineBreadcrumbs = () => { + const { headers } = useGuidelinesContext(); + return ( + <> + + Contents + + {headers.map(header => ( + + {header.text} + + ))} + + ); +}; + +export default GuidelineBreadcrumbs; diff --git a/components/pages/example/KnobsTable/KnobsTable.tsx b/components/pages/example/KnobsTable/KnobsTable.tsx index b07dbec2..04ce5108 100644 --- a/components/pages/example/KnobsTable/KnobsTable.tsx +++ b/components/pages/example/KnobsTable/KnobsTable.tsx @@ -1,4 +1,5 @@ -import { MouseEventHandler } from 'react'; +import { MouseEventHandler, useState } from 'react'; +import { Transition } from 'react-transition-group'; import Button from '@leafygreen-ui/button'; import Icon from '@leafygreen-ui/icon'; @@ -31,6 +32,9 @@ export const KnobsTable = ({ knobValues, updateKnobValue, }: KnobsTableProps) => { + const [showKnobs, setShowKnobs] = useState(false); + const toggleKnobs = () => setShowKnobs(s => !s); + return (
{codeExampleEnabled && ( @@ -39,23 +43,29 @@ export const KnobsTable = ({ className={exampleCodeButtonStyle} variant="default" size="xsmall" - onClick={handleShowCodeClick} + onClick={toggleKnobs} leftGlyph={ - + } > - {showCode ? 'Hide' : 'Show'} Code + {showKnobs ? 'Hide' : 'Show'} Controls
)} - {knobsArray.map(knob => ( - - ))} + {showKnobs && ( + + <> + {knobsArray.map(knob => ( + + ))} + + + )} ); }; diff --git a/components/pages/example/LiveExample.tsx b/components/pages/example/LiveExample.tsx index 939d9bec..59072c05 100644 --- a/components/pages/example/LiveExample.tsx +++ b/components/pages/example/LiveExample.tsx @@ -92,13 +92,12 @@ export const LiveExample = ({ // Currently disabling code examples for all components // TODO: Fix code examples: https://jira.mongodb.org/browse/LG-3310 - const codeExampleEnabled = false; //!disableCodeExampleFor.includes(componentName); + const codeExampleEnabled = true; //!disableCodeExampleFor.includes(componentName); return ( diff --git a/contexts/GuidelinesContext.tsx b/contexts/GuidelinesContext.tsx new file mode 100644 index 00000000..3aad09d9 --- /dev/null +++ b/contexts/GuidelinesContext.tsx @@ -0,0 +1,41 @@ +import { createContext, useContext, useState } from 'react'; + +export type BreadcrumbHeader = 'h2' | 'h4'; + +interface GuidelineHeader { + text: string; + type: BreadcrumbHeader; +} + +interface GuidelinesContextValue { + headers: Array; + pushHeader: (headerType: BreadcrumbHeader, headerText: string) => void; +} +const GuidelinesContext = createContext({ + headers: [], + pushHeader: () => {}, +}); + +export function GuidelinesContextProvider({ children }) { + const [headers, setHeaders] = useState>([]); + const pushHeader = (headerType: BreadcrumbHeader, headerText: string) => + setHeaders(oldHeaders => [ + ...oldHeaders, + { text: headerText, type: headerType }, + ]); + + return ( + + {children} + + ); +} + +export function useGuidelinesContext() { + return useContext(GuidelinesContext); +} diff --git a/layouts/NewComponentLayout.tsx b/layouts/NewComponentLayout.tsx index eb8eb08d..5ca4f5c2 100644 --- a/layouts/NewComponentLayout.tsx +++ b/layouts/NewComponentLayout.tsx @@ -1,34 +1,54 @@ import React from 'react'; -import styled from "@emotion/styled"; -import kebabCase from 'lodash/kebabCase'; +import styled from '@emotion/styled'; +import { GuidelinesContextProvider } from 'contexts/GuidelinesContext'; import startCase from 'lodash/startCase'; -import getFullPageTitle from "utils/getFullPageTitle"; -import { mq } from "utils/mediaQuery"; - -import { spacing } from "@leafygreen-ui/tokens"; -import { ComponentFields } from 'utils/ContentStack/types'; import Head from 'next/head'; -import { Body, H2 } from '@leafygreen-ui/typography'; import { useSession } from 'next-auth/react'; +import { ComponentFields } from 'utils/ContentStack/types'; +import getFullPageTitle from 'utils/getFullPageTitle'; +import { mq } from 'utils/mediaQuery'; +import { CustomComponentDoc } from 'utils/tsdoc.utils'; + +import GuidelineBreadcrumbs from 'components/GuidelineBreadcrumbs'; +import { LiveExample } from 'components/pages/example/LiveExample'; -const Main = styled('main')` +import LeafyGreenProvider from '@leafygreen-ui/leafygreen-provider'; +import { palette } from '@leafygreen-ui/palette'; +import { spacing } from '@leafygreen-ui/tokens'; +import { Body, H1, Overline } from '@leafygreen-ui/typography'; + +const Container = styled('div')` display: flex; justify-content: center; + position: relative; + ${mq({ + // 51px is a magic number for baseline alignment with the first SideNavGroup header + marginTop: [`${spacing[4]}px`, `${spacing[4]}px`, '51px'], + })} `; const ContentContainer = styled('div')` ${mq({ - // 51px is a magic number for baseline alignment with the first SideNavGroup header - marginTop: [`${spacing[4]}px`, `${spacing[4]}px`, '51px'], - paddingLeft: [`${spacing[2]}`, `${spacing[6]}px`, `${spacing[7]}px`], - paddingRight: [`${spacing[2]}`, `${spacing[6]}px`, `${spacing[7]}px`], - width: ['100%', '75%', '75%'], + paddingLeft: [`${spacing[2]}`, `${spacing[3]}px`, `${spacing[7]}px`], + paddingRight: [`${spacing[2]}`, `${spacing[3]}px`, `${spacing[7]}px`], + width: ['100%', '100%', '75%', '60%'], })} -` +`; +const BreadcrumbsContainer = styled('div')` + min-width: 250px; + max-width: 250px; + position: sticky; + height: fit-content; + ${mq({ + // 51px is a magic number for baseline alignment with the first SideNavGroup header + paddingTop: [`${spacing[4]}px`, `${spacing[4]}px`, '51px'], + })} + top: ${spacing[6]}px; +`; -const Header = styled(H2)` - margin-bottom: ${spacing[4]}px; +const Header = styled(H1)` + margin-bottom: ${spacing[3]}px; text-transform: capitalize; `; @@ -36,41 +56,58 @@ const NewComponentLayout = ({ component, componentName, children, + tsDoc, }: { component?: ComponentFields; children: React.ReactNode; componentName: string; + tsDoc: Array | null; }) => { const pageTitle = getFullPageTitle(startCase(componentName)); - console.log(component) const { data: session } = useSession(); return ( -
- - {pageTitle} - - {/* If the description field doesn't exist, it will default to a description of the site, defined in _document. */} - {component?.description && ( - - )} - - - {component && (!component.private || (component.private && session)) ? - -
- {componentName} -
- - {component.description} - - {children} -
- : children} -
- ) -} + +
+ + {pageTitle} + + {/* If the description field doesn't exist, it will default to a description of the site, defined in _document. */} + {component?.description && ( + + )} + + + + {component && + (!component.private || (component.private && session)) ? ( + + +
{componentName}
+ + {component.description} + +
+ + Playground + + +
+ {children} +
+ + + +
+ ) : ( + children + )} +
+
+
+ ); +}; NewComponentLayout.displayName = 'NewComponentLayout'; -export default NewComponentLayout; \ No newline at end of file +export default NewComponentLayout; diff --git a/pages/component/[componentName]/guidelines.tsx b/pages/component/[componentName]/guidelines.tsx index ea6bb1c8..2512d426 100644 --- a/pages/component/[componentName]/guidelines.tsx +++ b/pages/component/[componentName]/guidelines.tsx @@ -1,15 +1,16 @@ import { ReactElement } from 'react'; -import ComponentLayout from 'layouts/ComponentLayout'; +import NewComponentLayout from 'layouts/NewComponentLayout'; import { containerPadding } from 'styles/globals'; -import { - getStaticComponentPaths, - getStaticComponentProps, -} from 'utils/ContentStack/getStaticComponent'; +import { getTSDoc } from 'utils/_getComponentResources'; +import { getComponent } from 'utils/ContentStack/getContentstackResources'; +import { getStaticComponentPaths } from 'utils/ContentStack/getStaticComponent'; +import { ComponentPageMeta } from 'utils/ContentStack/types'; import isEmptyRichText from 'utils/isEmptyRichText'; import ComingSoon from 'components/ComingSoon'; import ContentstackRichText from 'components/ContentstackRichText'; -import NewComponentLayout from 'layouts/NewComponentLayout'; + +import { ExamplePageProps } from './example'; const ComponentGuidelines = ({ component }) => { const guidelines = component.designguidelines; @@ -27,6 +28,7 @@ ComponentGuidelines.getLayout = function getLayout(page: ReactElement) { {page} @@ -34,6 +36,24 @@ ComponentGuidelines.getLayout = function getLayout(page: ReactElement) { }; export const getStaticPaths = getStaticComponentPaths; -export const getStaticProps = getStaticComponentProps; +export const getStaticProps = async ({ + params, +}): Promise<{ props: ExamplePageProps }> => { + const { componentName } = params; + const component: ComponentPageMeta | null = + (await getComponent(componentName, { + includeContent: true, + })) ?? null; + + const tsDoc = await getTSDoc(componentName); + + return { + props: { + componentName, + component, + tsDoc, + }, + }; +}; export default ComponentGuidelines; diff --git a/utils/ContentStack/getContentstackResources.ts b/utils/ContentStack/getContentstackResources.ts index 7897b0a1..97d6e4b7 100644 --- a/utils/ContentStack/getContentstackResources.ts +++ b/utils/ContentStack/getContentstackResources.ts @@ -14,6 +14,7 @@ const ENV_MAP = { production: 'main', staging: 'staging', dev: 'staging', + experimental: 'experimental', } as const; const environment = ((): string => { @@ -32,6 +33,10 @@ const Stack = Contentstack.Stack({ api_key: process.env.NEXT_PUBLIC_CONTENTSTACK_API_KEY as string, delivery_token: process.env.NEXT_PUBLIC_CONTENTSTACK_DELIVERY_TOKEN as string, environment, + branch: + process.env.NEXT_PUBLIC_ENVIRONMENT === 'experimental' + ? 'experimental' + : 'main', }); interface QueryOptions { From 3a4ecc9da87c71fc1f02cbd218fde7fb7fe95762 Mon Sep 17 00:00:00 2001 From: spark33 Date: Tue, 24 Oct 2023 04:06:21 -0400 Subject: [PATCH 04/10] Style changes --- .../ContentstackRichText/HeaderContent.tsx | 4 +-- components/GuidelineBreadcrumbs.tsx | 29 +++++++++---------- layouts/NewComponentLayout.tsx | 27 +++++++++-------- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/components/ContentstackRichText/HeaderContent.tsx b/components/ContentstackRichText/HeaderContent.tsx index 3119b87a..de5c5eb4 100644 --- a/components/ContentstackRichText/HeaderContent.tsx +++ b/components/ContentstackRichText/HeaderContent.tsx @@ -1,3 +1,4 @@ +import { useEffect } from 'react'; import { TextNode } from '@contentstack/utils'; import styled from '@emotion/styled'; import { @@ -13,7 +14,6 @@ import { palette } from '@leafygreen-ui/palette'; import ContentstackChildren from './ContentstackChildren'; import { AnyNode, CSNode, CSTextNode } from './types'; import { getCSNodeTextContent } from './utils'; -import { useEffect } from 'react'; const StyledAnchor = styled('a')` color: inherit; @@ -67,7 +67,7 @@ const HeaderContent = ({ node }: { node: CSNode }) => { if (node.type === 'h2' || node.type === 'h4') { pushHeader(node.type as BreadcrumbHeader, nodeText); } - }, []) + }, []); return ( diff --git a/components/GuidelineBreadcrumbs.tsx b/components/GuidelineBreadcrumbs.tsx index cb175ce2..57a51835 100644 --- a/components/GuidelineBreadcrumbs.tsx +++ b/components/GuidelineBreadcrumbs.tsx @@ -1,30 +1,29 @@ import styled from '@emotion/styled'; -import { palette } from '@leafygreen-ui/palette'; -import { Overline } from '@leafygreen-ui/typography'; import { useGuidelinesContext } from 'contexts/GuidelinesContext'; import kebabCase from 'lodash/kebabCase'; -const BreadCrumb = styled('a')` +import { palette } from '@leafygreen-ui/palette'; +import { spacing } from '@leafygreen-ui/tokens'; +import { Overline } from '@leafygreen-ui/typography'; + +const BreadCrumb = styled('a')<{ $indented?: boolean }>` display: block; -` + padding-left: ${props => props.$indented ? spacing[2] : 0}px; +`; -const GuidelineBreadcrumb = ({ children, active = false }) => { - return ( - {children} - ) -} +const GuidelineBreadcrumb = ({ header }) => { + return + {header.text} + ; +}; const GuidelineBreadcrumbs = () => { const { headers } = useGuidelinesContext(); return ( <> - - Contents - + Contents {headers.map(header => ( - - {header.text} - + ))} ); diff --git a/layouts/NewComponentLayout.tsx b/layouts/NewComponentLayout.tsx index 5ca4f5c2..713f80c7 100644 --- a/layouts/NewComponentLayout.tsx +++ b/layouts/NewComponentLayout.tsx @@ -18,32 +18,33 @@ import { spacing } from '@leafygreen-ui/tokens'; import { Body, H1, Overline } from '@leafygreen-ui/typography'; const Container = styled('div')` - display: flex; - justify-content: center; - position: relative; ${mq({ // 51px is a magic number for baseline alignment with the first SideNavGroup header marginTop: [`${spacing[4]}px`, `${spacing[4]}px`, '51px'], + width: ['100%', '100%', '90%', '75%'], + paddingLeft: [`${spacing[2]}`, `${spacing[4]}px`, `120px`], + paddingRight: [`${spacing[2]}`, `${spacing[4]}px`, `120px`], })} `; -const ContentContainer = styled('div')` +const FlexContainer = styled('div')` + display: flex; + gap: ${spacing[7]}px; + position: relative; ${mq({ - paddingLeft: [`${spacing[2]}`, `${spacing[3]}px`, `${spacing[7]}px`], - paddingRight: [`${spacing[2]}`, `${spacing[3]}px`, `${spacing[7]}px`], - width: ['100%', '100%', '75%', '60%'], + flexDirection: ['column-reverse', 'row'] })} `; +const ContentContainer = styled('div')` + flex: 1; +`; + const BreadcrumbsContainer = styled('div')` min-width: 250px; max-width: 250px; position: sticky; height: fit-content; - ${mq({ - // 51px is a magic number for baseline alignment with the first SideNavGroup header - paddingTop: [`${spacing[4]}px`, `${spacing[4]}px`, '51px'], - })} top: ${spacing[6]}px; `; @@ -82,8 +83,9 @@ const NewComponentLayout = ({ {component && (!component.private || (component.private && session)) ? ( +
{componentName}
+ -
{componentName}
{component.description} @@ -98,6 +100,7 @@ const NewComponentLayout = ({ +
) : ( children From 78bf39c57ddea7a1665a3c93e0734cfdb6e09ace Mon Sep 17 00:00:00 2001 From: spark33 Date: Wed, 25 Oct 2023 14:12:55 -0400 Subject: [PATCH 05/10] styles --- .../ExampleCardBlock/ExampleCardBlock.tsx | 1 + components/GuidelineBreadcrumbs.tsx | 16 ++++- components/SignIn/DesktopSignIn.tsx | 2 + layouts/BaseLayout.tsx | 14 ++-- layouts/NewComponentLayout.tsx | 72 ++++++++++++++++--- next.config.js | 2 +- public/Blob.svg | 3 + .../ContentStack/getContentstackResources.ts | 1 + utils/ContentStack/types.ts | 13 ++++ utils/getGithubLink.tsx | 2 +- 10 files changed, 105 insertions(+), 21 deletions(-) create mode 100644 public/Blob.svg diff --git a/components/ContentstackRichText/ExampleCardBlock/ExampleCardBlock.tsx b/components/ContentstackRichText/ExampleCardBlock/ExampleCardBlock.tsx index b9ee0f81..306d205e 100644 --- a/components/ContentstackRichText/ExampleCardBlock/ExampleCardBlock.tsx +++ b/components/ContentstackRichText/ExampleCardBlock/ExampleCardBlock.tsx @@ -19,6 +19,7 @@ import ImageContainer from './ImageContainer'; const ExampleCardBlockWrapper = styled.div` margin-block: ${spacing[5] + spacing[2]}px; + width: 100%; `; const TextContainer = styled.div` diff --git a/components/GuidelineBreadcrumbs.tsx b/components/GuidelineBreadcrumbs.tsx index 57a51835..9ad4bf65 100644 --- a/components/GuidelineBreadcrumbs.tsx +++ b/components/GuidelineBreadcrumbs.tsx @@ -8,7 +8,18 @@ import { Overline } from '@leafygreen-ui/typography'; const BreadCrumb = styled('a')<{ $indented?: boolean }>` display: block; - padding-left: ${props => props.$indented ? spacing[2] : 0}px; + width: fit-content; + padding: 6px 12px; + margin-left: ${props => props.$indented ? spacing[3] : 0}px; + color: ${palette.gray.dark1}; + text-decoration: none; + border-radius: 24px; + + &:hover { + background-color: ${palette.green.light3}; + color: ${palette.green.dark2}; + font-weight: bold; + } `; const GuidelineBreadcrumb = ({ header }) => { @@ -21,7 +32,8 @@ const GuidelineBreadcrumbs = () => { const { headers } = useGuidelinesContext(); return ( <> - Contents + Contents + {headers.map(header => ( ))} diff --git a/components/SignIn/DesktopSignIn.tsx b/components/SignIn/DesktopSignIn.tsx index aaa4d8bb..decd6ef9 100644 --- a/components/SignIn/DesktopSignIn.tsx +++ b/components/SignIn/DesktopSignIn.tsx @@ -12,6 +12,8 @@ import { UserInfo } from '../UserInfo'; const Container = styled('div')` width: 100%; + position: absolute; + right: 16px; align-items: center; justify-content: flex-end; padding-top: ${spacing[4]}px; diff --git a/layouts/BaseLayout.tsx b/layouts/BaseLayout.tsx index 3f387ae5..8e776838 100644 --- a/layouts/BaseLayout.tsx +++ b/layouts/BaseLayout.tsx @@ -41,13 +41,13 @@ const padding = `${spacing[5]}px`; export const childrenWrapper = css` ${mq({ - paddingLeft: [0, 0, padding, padding], - paddingRight: [0, 0, padding, padding], - width: [ - '100%', - '100%', - `calc(100% - (${padding} * 2))', 'calc(1440px - 270px - (${padding} * 2))`, - ], + // paddingLeft: [0, 0, padding, padding], + // paddingRight: [0, 0, padding, padding], + // width: [ + // '100%', + // '100%', + // `calc(100% - (${padding} * 2))', 'calc(1440px - 270px - (${padding} * 2))`, + // ], minHeight: ['unset', 'unset', '100vh'], })} `; diff --git a/layouts/NewComponentLayout.tsx b/layouts/NewComponentLayout.tsx index 713f80c7..568c7152 100644 --- a/layouts/NewComponentLayout.tsx +++ b/layouts/NewComponentLayout.tsx @@ -12,16 +12,20 @@ import { CustomComponentDoc } from 'utils/tsdoc.utils'; import GuidelineBreadcrumbs from 'components/GuidelineBreadcrumbs'; import { LiveExample } from 'components/pages/example/LiveExample'; +import Button from '@leafygreen-ui/button'; import LeafyGreenProvider from '@leafygreen-ui/leafygreen-provider'; import { palette } from '@leafygreen-ui/palette'; import { spacing } from '@leafygreen-ui/tokens'; import { Body, H1, Overline } from '@leafygreen-ui/typography'; +import FigmaIcon from 'components/icons/FigmaIcon'; +import LinkIcon from '@leafygreen-ui/icon/dist/Link'; +import GithubIcon from 'components/icons/GithubIcon'; const Container = styled('div')` ${mq({ // 51px is a magic number for baseline alignment with the first SideNavGroup header marginTop: [`${spacing[4]}px`, `${spacing[4]}px`, '51px'], - width: ['100%', '100%', '90%', '75%'], + // width: ['100%', '100%', '90%', '75%'], paddingLeft: [`${spacing[2]}`, `${spacing[4]}px`, `120px`], paddingRight: [`${spacing[2]}`, `${spacing[4]}px`, `120px`], })} @@ -37,22 +41,49 @@ const FlexContainer = styled('div')` `; const ContentContainer = styled('div')` - flex: 1; + flex: 2; `; const BreadcrumbsContainer = styled('div')` - min-width: 250px; - max-width: 250px; position: sticky; height: fit-content; top: ${spacing[6]}px; + flex: 1; `; +const HeaderContainer = styled('div')` + background-color: ${palette.green.dark3}; + ${mq({ + padding: [`${spacing[2]}`, `${spacing[4]}px`, `${spacing[7]}px`], + paddingLeft: [`${spacing[2]}`, `${spacing[4]}px`, `120px`], + paddingRight: [`${spacing[2]}`, `${spacing[4]}px`, `120px`], + })} + background-image: url('/Blob.svg'); + background-repeat: no-repeat; + background-position: 100% 0%; +` + const Header = styled(H1)` margin-bottom: ${spacing[3]}px; text-transform: capitalize; `; +const getLinkIcon = (linkType) => { + switch (linkType) { + case 'Figma': + return + case 'Github': + return + default: + return + } +} + +const LinksContainer = styled('div')` + display: flex; + gap: ${spacing[2]}px; +`; + const NewComponentLayout = ({ component, componentName, @@ -77,19 +108,39 @@ const NewComponentLayout = ({ {component?.description && ( )} - + {component && (!component.private || (component.private && session)) ? ( + <> + + + {/* Add id for hard-coded breadcrumb */} +
{startCase(componentName)}
+ + {component.description} + + + {component.links_data && component.links_data.map(linkObject => ( + + ))} + +
+
-
{componentName}
- - {component.description} - -
+
Playground @@ -102,6 +153,7 @@ const NewComponentLayout = ({ + ) : ( children )} diff --git a/next.config.js b/next.config.js index 1940b296..0a38eebc 100644 --- a/next.config.js +++ b/next.config.js @@ -42,7 +42,7 @@ const nextConfig = { config.module.rules.push({ test: /\.svg$/, use: ['@svgr/webpack'], - include: isPathInLeafygreen, + // include: [isPathInLeafygreen, '/public'], }); return config; diff --git a/public/Blob.svg b/public/Blob.svg new file mode 100644 index 00000000..3a6ed2f0 --- /dev/null +++ b/public/Blob.svg @@ -0,0 +1,3 @@ + + + diff --git a/utils/ContentStack/getContentstackResources.ts b/utils/ContentStack/getContentstackResources.ts index 97d6e4b7..5e29ec24 100644 --- a/utils/ContentStack/getContentstackResources.ts +++ b/utils/ContentStack/getContentstackResources.ts @@ -50,6 +50,7 @@ const componentProperties = [ 'url', 'figmaurl', 'private', + 'links_data', ]; const optionalComponentProperties = ['designguidelines']; diff --git a/utils/ContentStack/types.ts b/utils/ContentStack/types.ts index 4fcb50e1..2acd709b 100644 --- a/utils/ContentStack/types.ts +++ b/utils/ContentStack/types.ts @@ -1,3 +1,10 @@ +export interface ContentstackMetadata { + uid: string; +} +export interface ContentstackObject { + _metadata: ContentstackMetadata; +} + export interface ContentPageGroup extends Object { uid: string; title: string; @@ -34,12 +41,18 @@ export interface ComponentPageMeta { private?: boolean; } +export interface LinkData extends ContentstackObject { + link: { title: string, href: string }; + type?: string; +} + /** * A {@link ComponentPageMeta} with additional data fields * including `figmaUrl` & `designguidelines` content */ export interface ComponentFields extends ComponentPageMeta { designguidelines?: unknown; + links_data?: Array<{ link_data: LinkData }>; } export interface BaseLayoutProps { diff --git a/utils/getGithubLink.tsx b/utils/getGithubLink.tsx index e74df25f..87a05058 100644 --- a/utils/getGithubLink.tsx +++ b/utils/getGithubLink.tsx @@ -10,4 +10,4 @@ export const getGithubLink = (isPrivate, componentTitle) => isPrivate ? GITHUB_ORGS.private : GITHUB_ORGS.public }/leafygreen-ui${isPrivate ? '-private' : ''}/tree/main/packages/${kebabCase( componentTitle, - )}`; + )}`; \ No newline at end of file From 236f52f9708270f588a5161a8ade52a1829f20ae Mon Sep 17 00:00:00 2001 From: spark33 Date: Thu, 26 Oct 2023 06:20:54 -0400 Subject: [PATCH 06/10] scrolling breadcrumbs --- .../ContentstackEntry.tsx | 4 + .../LiveExampleBlock/LiveExampleBlock.tsx | 91 ++++++++++++++ .../ContentstackRichText/componentMap.tsx | 74 ++++++----- components/ContentstackRichText/types.ts | 5 + components/GuidelineBreadcrumbs.tsx | 46 +++++-- contexts/GuidelinesContext.tsx | 103 ++++++++++++++- layouts/BaseLayout.tsx | 6 +- layouts/NewComponentLayout.tsx | 117 ++++++++++-------- utils/ContentStack/types.ts | 2 +- utils/getGithubLink.tsx | 2 +- 10 files changed, 349 insertions(+), 101 deletions(-) create mode 100644 components/ContentstackRichText/LiveExampleBlock/LiveExampleBlock.tsx diff --git a/components/ContentstackRichText/ContentstackEntry.tsx b/components/ContentstackRichText/ContentstackEntry.tsx index 78a5267d..e06e22f4 100644 --- a/components/ContentstackRichText/ContentstackEntry.tsx +++ b/components/ContentstackRichText/ContentstackEntry.tsx @@ -10,6 +10,7 @@ import ExpandableCard from '@leafygreen-ui/expandable-card'; import ArrowRight from '@leafygreen-ui/icon/dist/ArrowRight'; import { spacing } from '@leafygreen-ui/tokens'; +import LiveExampleBlock from './LiveExampleBlock/LiveExampleBlock'; import AnnotatedImageBlock from './AnnotatedImageBlock'; import BasicUsageBlock from './BasicUsageBlock'; import ExampleCardBlock from './ExampleCardBlock'; @@ -82,6 +83,9 @@ const blockToElementMap: { ), horizontal_layout: props => , + live_example_block: props => ( + + ), } as const; const ContentstackEntry = ({ diff --git a/components/ContentstackRichText/LiveExampleBlock/LiveExampleBlock.tsx b/components/ContentstackRichText/LiveExampleBlock/LiveExampleBlock.tsx new file mode 100644 index 00000000..353dc4c4 --- /dev/null +++ b/components/ContentstackRichText/LiveExampleBlock/LiveExampleBlock.tsx @@ -0,0 +1,91 @@ +import { useEffect, useState } from 'react'; +import { ComponentStoryFn } from '@storybook/react'; +import { useGuidelinesContext } from 'contexts/GuidelinesContext'; +import { getComponentStories, ModuleType } from 'utils/getComponentStories'; + +import { useAsyncEffect } from 'components/pages/example/useAsyncEffect'; + +import Button from '@leafygreen-ui/button'; +import Card from '@leafygreen-ui/card'; +import Code from '@leafygreen-ui/code'; +import Icon from '@leafygreen-ui/icon'; +import { spacing } from '@leafygreen-ui/tokens'; + +const LiveExampleBlock = ({ storyName }: { storyName: string }) => { + const [StoryFn, setStoryFn] = useState< + ComponentStoryFn & React.FunctionComponent + >(); + const [args, setArgs] = useState({}); + const [sourceCode, setSourceCode] = useState(); + const { componentName } = useGuidelinesContext(); + const [showCode, setShowCode] = useState(true); + const toggleShowCode = () => setShowCode(sc => !sc); + + useAsyncEffect( + // @ts-ignore conditional checked in useAsyncEffect + () => getComponentStories(componentName), + { + then: module => { + if (module) { + const { default: meta, ...stories } = module; + setStoryFn(() => stories[storyName]); + setArgs({ ...meta.args, ...stories[storyName].args }); + setSourceCode(stories[storyName]?.parameters?.sourceCode); + } else { + console.error('Error parsing module', module); + } + }, + catch: err => { + console.error('Error loading LiveExample'); + console.error(err); + }, + }, + !!componentName, + [componentName], + ); + + return ( + <> + {StoryFn ? ( +
+ +
+
+ +
+
+ +
+
+
+ {showCode && sourceCode && {sourceCode}} +
+ ) : ( + <>Loading... + )} + + ); +}; + +export default LiveExampleBlock; diff --git a/components/ContentstackRichText/componentMap.tsx b/components/ContentstackRichText/componentMap.tsx index 1a2da261..b31a741d 100644 --- a/components/ContentstackRichText/componentMap.tsx +++ b/components/ContentstackRichText/componentMap.tsx @@ -1,4 +1,6 @@ +import { useEffect } from 'react'; import { css } from '@emotion/react'; +import { useGuidelinesContext } from 'contexts/GuidelinesContext'; import Card from '@leafygreen-ui/card'; import { palette } from '@leafygreen-ui/palette'; @@ -17,6 +19,7 @@ import ContentstackChildren from './ContentstackChildren'; import ContentstackReference from './ContentstackReference'; import HeaderContent from './HeaderContent'; import { CSNode, CSNodeType, CSTextNode } from './types'; +import { getCSNodeTextContent } from './utils'; type CSNodeTypeMapFunction = ( node: CSNode | CSTextNode, @@ -48,23 +51,29 @@ export const nodeTypeToElementMap: { ), - [CSNodeType.HEADING_2]: (node, props) => ( -

{ + const { getHeaderRef } = useGuidelinesContext(); + const nodeText = getCSNodeTextContent(node); + const headerRef = getHeaderRef(nodeText); + return ( +

- -

- ), + &:not(:first-child) { + margin-top: ${spacing[6]}px; + } + ` + } + ref={headerRef} + {...props} + > + + + ); + }, [CSNodeType.HEADING_3]: (node, props) => (

), - [CSNodeType.HEADING_4]: (node, props) => ( - - - - ), + [CSNodeType.HEADING_4]: (node, props) => { + const { getHeaderRef } = useGuidelinesContext(); + const nodeText = getCSNodeTextContent(node); + const headerRef = getHeaderRef(nodeText); + // useEffect(() => { console.log(headerRef?.current)}, [headerRef]) + return ( + + + + ); + }, [CSNodeType.HEADING_5]: (node, props) => ( diff --git a/components/ContentstackRichText/types.ts b/components/ContentstackRichText/types.ts index 1e6ecf68..d16b2da6 100644 --- a/components/ContentstackRichText/types.ts +++ b/components/ContentstackRichText/types.ts @@ -148,6 +148,10 @@ export interface TwoColumnExampleCardBlockProps { column_2_image: CSImage; } +export interface LiveExampleBlockProps { + story_name: string; +} + export interface BlockPropsMap { annotated_image_block: AnnotatedImageBlockProps; badge_block: BadgeBlockProps; @@ -159,6 +163,7 @@ export interface BlockPropsMap { expandable_card_block: ExpandableCardBlockProps; horizontal_layout: HorizontalLayoutBlockProps; example_card_block_2_column_: TwoColumnExampleCardBlockProps; + live_example_block: LiveExampleBlockProps; } export type ContentTypeUID = keyof BlockPropsMap; diff --git a/components/GuidelineBreadcrumbs.tsx b/components/GuidelineBreadcrumbs.tsx index 9ad4bf65..3f20bfa7 100644 --- a/components/GuidelineBreadcrumbs.tsx +++ b/components/GuidelineBreadcrumbs.tsx @@ -6,37 +6,57 @@ import { palette } from '@leafygreen-ui/palette'; import { spacing } from '@leafygreen-ui/tokens'; import { Overline } from '@leafygreen-ui/typography'; -const BreadCrumb = styled('a')<{ $indented?: boolean }>` +const BreadCrumb = styled('a')<{ $indented?: boolean; active: boolean }>` display: block; width: fit-content; padding: 6px 12px; - margin-left: ${props => props.$indented ? spacing[3] : 0}px; + margin-left: ${props => (props.$indented ? spacing[3] : 0)}px; color: ${palette.gray.dark1}; text-decoration: none; border-radius: 24px; - &:hover { + ${props => + props.active && + ` background-color: ${palette.green.light3}; color: ${palette.green.dark2}; font-weight: bold; - } + `} `; -const GuidelineBreadcrumb = ({ header }) => { - return - {header.text} - ; +const GuidelineBreadcrumb = ({ header, active }) => { + return ( + + {header.text} + + ); }; const GuidelineBreadcrumbs = () => { - const { headers } = useGuidelinesContext(); + const { headers, activeHeaderIndex } = useGuidelinesContext(); return ( <> - Contents - - {headers.map(header => ( - + + Contents + + {headers.map((header, i) => ( + ))} + Scroll to top ); }; diff --git a/contexts/GuidelinesContext.tsx b/contexts/GuidelinesContext.tsx index 3aad09d9..d1a07835 100644 --- a/contexts/GuidelinesContext.tsx +++ b/contexts/GuidelinesContext.tsx @@ -1,4 +1,6 @@ -import { createContext, useContext, useState } from 'react'; +import { createContext, useContext, useEffect, useState } from 'react'; + +import { useDynamicRefs } from '@leafygreen-ui/hooks'; export type BreadcrumbHeader = 'h2' | 'h4'; @@ -10,25 +12,122 @@ interface GuidelineHeader { interface GuidelinesContextValue { headers: Array; pushHeader: (headerType: BreadcrumbHeader, headerText: string) => void; + activeHeaderIndex: number; + componentName?: string; + getHeaderRef: ( + key?: string | undefined, + ) => React.RefObject | undefined; } const GuidelinesContext = createContext({ headers: [], pushHeader: () => {}, + activeHeaderIndex: 0, + componentName: '', + getHeaderRef: () => undefined, }); -export function GuidelinesContextProvider({ children }) { +/** + * https://codesandbox.io/s/highlight-menu-item-on-scroll-x0gzn?file=/src/ScrollHighlightNabbar/ScrollHighlightNabbar.js:127-1523 + * @param {number} currentPosition Current Scroll position + * @param {Array} sectionPositionArray Array of positions of all sections + * @param {number} startIndex Start index of array + * @param {number} endIndex End index of array + * @return {number} Current Active index + */ +const nearestScrolledRefIndex = ( + currentPosition, + headers, + startIndex, + endIndex, + getHeaderRef, +) => { + if (startIndex === endIndex) return startIndex; + else if (startIndex === endIndex - 1) { + if ( + Math.abs( + getHeaderRef(headers[startIndex].text)?.current.offsetTop - + currentPosition, + ) < + Math.abs( + getHeaderRef(headers[endIndex].text)?.current.offsetTop - + currentPosition, + ) + ) + return startIndex; + else return endIndex; + } else { + const nextNearest = ~~((startIndex + endIndex) / 2); + const a = Math.abs( + getHeaderRef(headers[nextNearest].text)?.current.offsetTop - + currentPosition, + ); + const b = Math.abs( + getHeaderRef(headers[nextNearest + 1].text)?.current.offsetTop - + currentPosition, + ); + + if (a < b) { + return nearestScrolledRefIndex( + currentPosition, + headers, + startIndex, + nextNearest, + getHeaderRef, + ); + } else { + return nearestScrolledRefIndex( + currentPosition, + headers, + nextNearest, + endIndex, + getHeaderRef, + ); + } + } +}; + +export function GuidelinesContextProvider({ children, componentName }) { const [headers, setHeaders] = useState>([]); const pushHeader = (headerType: BreadcrumbHeader, headerText: string) => setHeaders(oldHeaders => [ ...oldHeaders, { text: headerText, type: headerType }, ]); + const getHeaderRef = useDynamicRefs({ prefix: 'guideline-header' }); + const [activeHeaderIndex, setActiveHeaderIndex] = useState(0); + + useEffect(() => { + if (headers.length > 0) { + const scrollContainer = document.querySelector('#scroll-container'); + + if (scrollContainer) { + const handleScroll = e => { + const index = nearestScrolledRefIndex( + scrollContainer.scrollTop, + headers, + 0, + headers.length - 1, + getHeaderRef, + ); + setActiveHeaderIndex(index - 1); + }; + // console.log(document.querySelector('')) + scrollContainer.addEventListener('scroll', handleScroll); + return () => { + scrollContainer.removeEventListener('scroll', handleScroll); + }; + } + } + }, [headers]); return ( {children} diff --git a/layouts/BaseLayout.tsx b/layouts/BaseLayout.tsx index 8e776838..f1768707 100644 --- a/layouts/BaseLayout.tsx +++ b/layouts/BaseLayout.tsx @@ -85,7 +85,11 @@ function BaseLayout({ children }: { children: React.ReactNode }) {
-
+
{children} diff --git a/layouts/NewComponentLayout.tsx b/layouts/NewComponentLayout.tsx index 568c7152..231615d2 100644 --- a/layouts/NewComponentLayout.tsx +++ b/layouts/NewComponentLayout.tsx @@ -1,6 +1,10 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import styled from '@emotion/styled'; -import { GuidelinesContextProvider } from 'contexts/GuidelinesContext'; +import { ComponentStoryFn } from '@storybook/react'; +import { + GuidelinesContextProvider, + useGuidelinesContext, +} from 'contexts/GuidelinesContext'; import startCase from 'lodash/startCase'; import Head from 'next/head'; import { useSession } from 'next-auth/react'; @@ -10,24 +14,24 @@ import { mq } from 'utils/mediaQuery'; import { CustomComponentDoc } from 'utils/tsdoc.utils'; import GuidelineBreadcrumbs from 'components/GuidelineBreadcrumbs'; +import FigmaIcon from 'components/icons/FigmaIcon'; +import GithubIcon from 'components/icons/GithubIcon'; import { LiveExample } from 'components/pages/example/LiveExample'; import Button from '@leafygreen-ui/button'; +import LinkIcon from '@leafygreen-ui/icon/dist/Link'; import LeafyGreenProvider from '@leafygreen-ui/leafygreen-provider'; import { palette } from '@leafygreen-ui/palette'; import { spacing } from '@leafygreen-ui/tokens'; import { Body, H1, Overline } from '@leafygreen-ui/typography'; -import FigmaIcon from 'components/icons/FigmaIcon'; -import LinkIcon from '@leafygreen-ui/icon/dist/Link'; -import GithubIcon from 'components/icons/GithubIcon'; const Container = styled('div')` ${mq({ // 51px is a magic number for baseline alignment with the first SideNavGroup header marginTop: [`${spacing[4]}px`, `${spacing[4]}px`, '51px'], // width: ['100%', '100%', '90%', '75%'], - paddingLeft: [`${spacing[2]}`, `${spacing[4]}px`, `120px`], - paddingRight: [`${spacing[2]}`, `${spacing[4]}px`, `120px`], + paddingLeft: [`${spacing[2]}`, `${spacing[4]}px`, '120px'], + paddingRight: [`${spacing[2]}`, `${spacing[4]}px`, '120px'], })} `; @@ -36,7 +40,7 @@ const FlexContainer = styled('div')` gap: ${spacing[7]}px; position: relative; ${mq({ - flexDirection: ['column-reverse', 'row'] + flexDirection: ['column-reverse', 'row'], })} `; @@ -55,29 +59,29 @@ const HeaderContainer = styled('div')` background-color: ${palette.green.dark3}; ${mq({ padding: [`${spacing[2]}`, `${spacing[4]}px`, `${spacing[7]}px`], - paddingLeft: [`${spacing[2]}`, `${spacing[4]}px`, `120px`], - paddingRight: [`${spacing[2]}`, `${spacing[4]}px`, `120px`], + paddingLeft: [`${spacing[2]}`, `${spacing[4]}px`, '120px'], + paddingRight: [`${spacing[2]}`, `${spacing[4]}px`, '120px'], })} background-image: url('/Blob.svg'); background-repeat: no-repeat; background-position: 100% 0%; -` +`; const Header = styled(H1)` margin-bottom: ${spacing[3]}px; text-transform: capitalize; `; -const getLinkIcon = (linkType) => { +const getLinkIcon = linkType => { switch (linkType) { case 'Figma': - return + return ; case 'Github': - return + return ; default: - return + return ; } -} +}; const LinksContainer = styled('div')` display: flex; @@ -99,8 +103,9 @@ const NewComponentLayout = ({ const { data: session } = useSession(); return ( - +
+
{pageTitle} @@ -115,44 +120,48 @@ const NewComponentLayout = ({ (!component.private || (component.private && session)) ? ( <> - - {/* Add id for hard-coded breadcrumb */} -
{startCase(componentName)}
- - {component.description} - - - {component.links_data && component.links_data.map(linkObject => ( - - ))} - -
+ + {/* Add id for hard-coded breadcrumb */} +
{startCase(componentName)}
+ + {component.description} + + + {component.links_data && + component.links_data.map(linkObject => ( + + ))} + +
- - - -
- - Playground - - -
- {children} -
- - - -
-
+ + + +
+ + Playground + + +
+ {children} +
+ + + +
+
) : ( children diff --git a/utils/ContentStack/types.ts b/utils/ContentStack/types.ts index 2acd709b..05e3756a 100644 --- a/utils/ContentStack/types.ts +++ b/utils/ContentStack/types.ts @@ -42,7 +42,7 @@ export interface ComponentPageMeta { } export interface LinkData extends ContentstackObject { - link: { title: string, href: string }; + link: { title: string; href: string }; type?: string; } diff --git a/utils/getGithubLink.tsx b/utils/getGithubLink.tsx index 87a05058..e74df25f 100644 --- a/utils/getGithubLink.tsx +++ b/utils/getGithubLink.tsx @@ -10,4 +10,4 @@ export const getGithubLink = (isPrivate, componentTitle) => isPrivate ? GITHUB_ORGS.private : GITHUB_ORGS.public }/leafygreen-ui${isPrivate ? '-private' : ''}/tree/main/packages/${kebabCase( componentTitle, - )}`; \ No newline at end of file + )}`; From cc9bb42200fd0185d4d404c1bb89431c2ffe2840 Mon Sep 17 00:00:00 2001 From: spark33 Date: Thu, 26 Oct 2023 06:30:58 -0400 Subject: [PATCH 07/10] Style embedded live example --- .../LiveExampleBlock/LiveExampleBlock.tsx | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/components/ContentstackRichText/LiveExampleBlock/LiveExampleBlock.tsx b/components/ContentstackRichText/LiveExampleBlock/LiveExampleBlock.tsx index 353dc4c4..092d8fec 100644 --- a/components/ContentstackRichText/LiveExampleBlock/LiveExampleBlock.tsx +++ b/components/ContentstackRichText/LiveExampleBlock/LiveExampleBlock.tsx @@ -1,7 +1,8 @@ -import { useEffect, useState } from 'react'; +import { useState } from 'react'; +import styled from '@emotion/styled'; import { ComponentStoryFn } from '@storybook/react'; import { useGuidelinesContext } from 'contexts/GuidelinesContext'; -import { getComponentStories, ModuleType } from 'utils/getComponentStories'; +import { getComponentStories } from 'utils/getComponentStories'; import { useAsyncEffect } from 'components/pages/example/useAsyncEffect'; @@ -11,6 +12,22 @@ import Code from '@leafygreen-ui/code'; import Icon from '@leafygreen-ui/icon'; import { spacing } from '@leafygreen-ui/tokens'; +const CodeWrapper = styled('div')` + > div { + border-left: 0; + border-right: 0; + border-bottom: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; + overflow: hidden; + + & > div:before, + & > div:after { + z-index: 0; + } + } +`; + const LiveExampleBlock = ({ storyName }: { storyName: string }) => { const [StoryFn, setStoryFn] = useState< ComponentStoryFn & React.FunctionComponent @@ -48,7 +65,7 @@ const LiveExampleBlock = ({ storyName }: { storyName: string }) => { <> {StoryFn ? (
- +
{ width: '100%', display: 'flex', justifyContent: 'flex-end', + padding: `${spacing[3]}px`, }} >
+ {showCode && sourceCode && {sourceCode}} - {showCode && sourceCode && {sourceCode}}
) : ( <>Loading... From 48b806170c25d2e4d6ec4b17c26807d0b5949cb2 Mon Sep 17 00:00:00 2001 From: spark33 Date: Thu, 26 Oct 2023 11:05:49 -0400 Subject: [PATCH 08/10] demo ready --- .../LiveExampleBlock/LiveExampleBlock.tsx | 8 +++- .../ContentstackRichText/componentMap.tsx | 9 +++-- components/GuidelineBreadcrumbs.tsx | 2 +- components/SignIn/DesktopSignIn.tsx | 4 +- components/navigation/NavigationContent.tsx | 6 +-- .../pages/example/KnobsTable/KnobRow.tsx | 4 +- .../pages/example/KnobsTable/KnobsTable.tsx | 12 +++++- components/pages/example/LiveExample.tsx | 1 + contexts/GuidelinesContext.tsx | 39 +++++++++++++------ .../{guidelines.tsx => index.tsx} | 0 10 files changed, 59 insertions(+), 26 deletions(-) rename pages/component/[componentName]/{guidelines.tsx => index.tsx} (100%) diff --git a/components/ContentstackRichText/LiveExampleBlock/LiveExampleBlock.tsx b/components/ContentstackRichText/LiveExampleBlock/LiveExampleBlock.tsx index 092d8fec..e14afbca 100644 --- a/components/ContentstackRichText/LiveExampleBlock/LiveExampleBlock.tsx +++ b/components/ContentstackRichText/LiveExampleBlock/LiveExampleBlock.tsx @@ -65,7 +65,7 @@ const LiveExampleBlock = ({ storyName }: { storyName: string }) => { <> {StoryFn ? (
- +
{
- {showCode && sourceCode && {sourceCode}} + {showCode && sourceCode && ( + + {sourceCode} + + )}
) : ( diff --git a/components/ContentstackRichText/componentMap.tsx b/components/ContentstackRichText/componentMap.tsx index b31a741d..06a68857 100644 --- a/components/ContentstackRichText/componentMap.tsx +++ b/components/ContentstackRichText/componentMap.tsx @@ -52,9 +52,9 @@ export const nodeTypeToElementMap: { ), [CSNodeType.HEADING_2]: (node, props) => { - const { getHeaderRef } = useGuidelinesContext(); + const { componentName, getHeaderRef } = useGuidelinesContext(); const nodeText = getCSNodeTextContent(node); - const headerRef = getHeaderRef(nodeText); + const headerRef = getHeaderRef(`${componentName}-${nodeText}`); return (

), [CSNodeType.HEADING_4]: (node, props) => { - const { getHeaderRef } = useGuidelinesContext(); + const { getHeaderRef, componentName } = useGuidelinesContext(); const nodeText = getCSNodeTextContent(node); - const headerRef = getHeaderRef(nodeText); + const headerRef = getHeaderRef(`${componentName}-${nodeText}`); // useEffect(() => { console.log(headerRef?.current)}, [headerRef]) return ( { active={i === activeHeaderIndex} /> ))} - Scroll to top + {/* Scroll to top */} ); }; diff --git a/components/SignIn/DesktopSignIn.tsx b/components/SignIn/DesktopSignIn.tsx index decd6ef9..eeff5a74 100644 --- a/components/SignIn/DesktopSignIn.tsx +++ b/components/SignIn/DesktopSignIn.tsx @@ -13,7 +13,7 @@ import { UserInfo } from '../UserInfo'; const Container = styled('div')` width: 100%; position: absolute; - right: 16px; + padding-right: 16px; align-items: center; justify-content: flex-end; padding-top: ${spacing[4]}px; @@ -76,7 +76,7 @@ const DesktopSignIn = () => { ) : (

diff --git a/components/pages/example/LiveExample.tsx b/components/pages/example/LiveExample.tsx index 59072c05..440292e6 100644 --- a/components/pages/example/LiveExample.tsx +++ b/components/pages/example/LiveExample.tsx @@ -7,6 +7,7 @@ import { usePrevious } from '@leafygreen-ui/hooks'; import LeafyGreenProvider, { useDarkMode, } from '@leafygreen-ui/leafygreen-provider'; +import { palette } from '@leafygreen-ui/palette'; import { KnobsTable } from './KnobsTable/KnobsTable'; import { isStateReady } from './useLiveExampleState/utils'; diff --git a/contexts/GuidelinesContext.tsx b/contexts/GuidelinesContext.tsx index d1a07835..b71ab5db 100644 --- a/contexts/GuidelinesContext.tsx +++ b/contexts/GuidelinesContext.tsx @@ -1,6 +1,7 @@ import { createContext, useContext, useEffect, useState } from 'react'; +import { useRouter } from 'next/router'; -import { useDynamicRefs } from '@leafygreen-ui/hooks'; +import { useDynamicRefs, usePrevious } from '@leafygreen-ui/hooks'; export type BreadcrumbHeader = 'h2' | 'h4'; @@ -40,17 +41,18 @@ const nearestScrolledRefIndex = ( startIndex, endIndex, getHeaderRef, + componentName, ) => { if (startIndex === endIndex) return startIndex; else if (startIndex === endIndex - 1) { if ( Math.abs( - getHeaderRef(headers[startIndex].text)?.current.offsetTop - - currentPosition, + getHeaderRef(`${componentName}-${headers[startIndex].text}`)?.current + .offsetTop - currentPosition, ) < Math.abs( - getHeaderRef(headers[endIndex].text)?.current.offsetTop - - currentPosition, + getHeaderRef(`${componentName}-${headers[endIndex].text}`)?.current + .offsetTop - currentPosition, ) ) return startIndex; @@ -58,12 +60,12 @@ const nearestScrolledRefIndex = ( } else { const nextNearest = ~~((startIndex + endIndex) / 2); const a = Math.abs( - getHeaderRef(headers[nextNearest].text)?.current.offsetTop - - currentPosition, + getHeaderRef(`${componentName}-${headers[nextNearest].text}`)?.current + .offsetTop - currentPosition, ); const b = Math.abs( - getHeaderRef(headers[nextNearest + 1].text)?.current.offsetTop - - currentPosition, + getHeaderRef(`${componentName}-${headers[nextNearest + 1].text}`)?.current + .offsetTop - currentPosition, ); if (a < b) { @@ -73,6 +75,7 @@ const nearestScrolledRefIndex = ( startIndex, nextNearest, getHeaderRef, + componentName, ); } else { return nearestScrolledRefIndex( @@ -81,19 +84,23 @@ const nearestScrolledRefIndex = ( nextNearest, endIndex, getHeaderRef, + componentName, ); } } }; export function GuidelinesContextProvider({ children, componentName }) { + const router = useRouter(); const [headers, setHeaders] = useState>([]); const pushHeader = (headerType: BreadcrumbHeader, headerText: string) => setHeaders(oldHeaders => [ ...oldHeaders, { text: headerText, type: headerType }, ]); - const getHeaderRef = useDynamicRefs({ prefix: 'guideline-header' }); + const getHeaderRef = useDynamicRefs({ + prefix: `${componentName}-guideline-header`, + }); const [activeHeaderIndex, setActiveHeaderIndex] = useState(0); useEffect(() => { @@ -108,10 +115,10 @@ export function GuidelinesContextProvider({ children, componentName }) { 0, headers.length - 1, getHeaderRef, + componentName, ); setActiveHeaderIndex(index - 1); }; - // console.log(document.querySelector('')) scrollContainer.addEventListener('scroll', handleScroll); return () => { scrollContainer.removeEventListener('scroll', handleScroll); @@ -120,6 +127,16 @@ export function GuidelinesContextProvider({ children, componentName }) { } }, [headers]); + useEffect(() => { + const startHandler = () => { + setHeaders([]); + }; + router.events.on('routeChangeStart', startHandler); + return () => { + router.events.off('routeChangeStart', startHandler); + }; + }, []); + return ( Date: Mon, 8 Jan 2024 23:30:33 -0500 Subject: [PATCH 09/10] fix build issues --- components/navigation/NavigationContent.tsx | 1 - pages/[contentPageGroup]/[contentPageTitle].tsx | 2 -- 2 files changed, 3 deletions(-) diff --git a/components/navigation/NavigationContent.tsx b/components/navigation/NavigationContent.tsx index f9d3d53e..6c00168b 100644 --- a/components/navigation/NavigationContent.tsx +++ b/components/navigation/NavigationContent.tsx @@ -1,4 +1,3 @@ -import { css } from '@emotion/react'; import styled from '@emotion/styled'; import { useAppContext } from 'contexts/AppContext'; import kebabCase from 'lodash/kebabCase'; diff --git a/pages/[contentPageGroup]/[contentPageTitle].tsx b/pages/[contentPageGroup]/[contentPageTitle].tsx index da20400d..fa62d802 100644 --- a/pages/[contentPageGroup]/[contentPageTitle].tsx +++ b/pages/[contentPageGroup]/[contentPageTitle].tsx @@ -2,8 +2,6 @@ import { ReactElement } from 'react'; import ContentPageLayout from 'layouts/ContentPageLayout'; import kebabCase from 'lodash/kebabCase'; import startCase from 'lodash/startCase'; -import { unstable_getServerSession } from 'next-auth'; -import { authOptions } from 'pages/api/auth/[...nextauth]'; import { getContentPage, getContentPageGroups, From 2e3ff461f302e11a288318f0260dd67a18f850ef Mon Sep 17 00:00:00 2001 From: spark33 Date: Mon, 8 Jan 2024 23:56:50 -0500 Subject: [PATCH 10/10] fix ts issues --- components/ContentstackRichText/ContentstackText.tsx | 1 + components/ContentstackRichText/HeaderContent.tsx | 4 +--- components/ContentstackRichText/componentMap.tsx | 1 - components/pages/example/LiveExample.tsx | 1 - contexts/GuidelinesContext.tsx | 2 +- layouts/BaseLayout.tsx | 2 -- layouts/NewComponentLayout.tsx | 8 ++------ 7 files changed, 5 insertions(+), 14 deletions(-) diff --git a/components/ContentstackRichText/ContentstackText.tsx b/components/ContentstackRichText/ContentstackText.tsx index 34d89bfa..36631c2b 100644 --- a/components/ContentstackRichText/ContentstackText.tsx +++ b/components/ContentstackRichText/ContentstackText.tsx @@ -24,6 +24,7 @@ const ContentstackText = ({ node, ...rest }: CSRichTextProps) => { } return ( + // @ts-ignore href not needed as no links will be rendered as a result of this component's logic {node.text} diff --git a/components/ContentstackRichText/HeaderContent.tsx b/components/ContentstackRichText/HeaderContent.tsx index de5c5eb4..e813af2c 100644 --- a/components/ContentstackRichText/HeaderContent.tsx +++ b/components/ContentstackRichText/HeaderContent.tsx @@ -1,5 +1,4 @@ import { useEffect } from 'react'; -import { TextNode } from '@contentstack/utils'; import styled from '@emotion/styled'; import { BreadcrumbHeader, @@ -11,8 +10,7 @@ import Link from 'next/link'; import Icon from '@leafygreen-ui/icon'; import { palette } from '@leafygreen-ui/palette'; -import ContentstackChildren from './ContentstackChildren'; -import { AnyNode, CSNode, CSTextNode } from './types'; +import { CSNode } from './types'; import { getCSNodeTextContent } from './utils'; const StyledAnchor = styled('a')` diff --git a/components/ContentstackRichText/componentMap.tsx b/components/ContentstackRichText/componentMap.tsx index 06a68857..ad46a2e0 100644 --- a/components/ContentstackRichText/componentMap.tsx +++ b/components/ContentstackRichText/componentMap.tsx @@ -1,4 +1,3 @@ -import { useEffect } from 'react'; import { css } from '@emotion/react'; import { useGuidelinesContext } from 'contexts/GuidelinesContext'; diff --git a/components/pages/example/LiveExample.tsx b/components/pages/example/LiveExample.tsx index 440292e6..59072c05 100644 --- a/components/pages/example/LiveExample.tsx +++ b/components/pages/example/LiveExample.tsx @@ -7,7 +7,6 @@ import { usePrevious } from '@leafygreen-ui/hooks'; import LeafyGreenProvider, { useDarkMode, } from '@leafygreen-ui/leafygreen-provider'; -import { palette } from '@leafygreen-ui/palette'; import { KnobsTable } from './KnobsTable/KnobsTable'; import { isStateReady } from './useLiveExampleState/utils'; diff --git a/contexts/GuidelinesContext.tsx b/contexts/GuidelinesContext.tsx index b71ab5db..411650b0 100644 --- a/contexts/GuidelinesContext.tsx +++ b/contexts/GuidelinesContext.tsx @@ -1,7 +1,7 @@ import { createContext, useContext, useEffect, useState } from 'react'; import { useRouter } from 'next/router'; -import { useDynamicRefs, usePrevious } from '@leafygreen-ui/hooks'; +import { useDynamicRefs } from '@leafygreen-ui/hooks'; export type BreadcrumbHeader = 'h2' | 'h4'; diff --git a/layouts/BaseLayout.tsx b/layouts/BaseLayout.tsx index f1768707..fe31a312 100644 --- a/layouts/BaseLayout.tsx +++ b/layouts/BaseLayout.tsx @@ -37,8 +37,6 @@ const layout = css` })} `; -const padding = `${spacing[5]}px`; - export const childrenWrapper = css` ${mq({ // paddingLeft: [0, 0, padding, padding], diff --git a/layouts/NewComponentLayout.tsx b/layouts/NewComponentLayout.tsx index 231615d2..f0ef1f6f 100644 --- a/layouts/NewComponentLayout.tsx +++ b/layouts/NewComponentLayout.tsx @@ -1,10 +1,6 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import styled from '@emotion/styled'; -import { ComponentStoryFn } from '@storybook/react'; -import { - GuidelinesContextProvider, - useGuidelinesContext, -} from 'contexts/GuidelinesContext'; +import { GuidelinesContextProvider } from 'contexts/GuidelinesContext'; import startCase from 'lodash/startCase'; import Head from 'next/head'; import { useSession } from 'next-auth/react';