diff --git a/common/__generated__/graphql.ts b/common/__generated__/graphql.ts index 1567cde8..8caf0271 100644 --- a/common/__generated__/graphql.ts +++ b/common/__generated__/graphql.ts @@ -8460,7 +8460,13 @@ export type TemplatedCategoryPageFragmentFragment = ( ) | { __typename: 'CategoryPageBodyBlock' | 'CategoryPageCategoryListBlock' } | ( { heading?: string | null, description?: string | null } & { __typename: 'CategoryPageContactFormBlock' } - )> | null, layoutAside?: Array<{ __typename: 'CategoryPageAttributeTypeBlock' }> | null } + )> | null, layoutAside?: Array<( + { attributeType: ( + { identifier: string } + & { __typename?: 'AttributeType' } + ) } + & { __typename: 'CategoryPageAttributeTypeBlock' } + )> | null } & { __typename: 'CategoryTypePageLevelLayout' } ) | null } & { __typename?: 'CategoryPage' } @@ -9318,7 +9324,13 @@ export type GetPlanPageGeneralQuery = ( ) | { __typename: 'CategoryPageBodyBlock' | 'CategoryPageCategoryListBlock' } | ( { heading?: string | null, description?: string | null } & { __typename: 'CategoryPageContactFormBlock' } - )> | null, layoutAside?: Array<{ __typename: 'CategoryPageAttributeTypeBlock' }> | null } + )> | null, layoutAside?: Array<( + { attributeType: ( + { identifier: string } + & { __typename?: 'AttributeType' } + ) } + & { __typename: 'CategoryPageAttributeTypeBlock' } + )> | null } & { __typename: 'CategoryTypePageLevelLayout' } ) | null } & { __typename: 'CategoryPage' } diff --git a/components/categories/CategoryPageContent.tsx b/components/categories/CategoryPageContent.tsx new file mode 100644 index 00000000..1fcff422 --- /dev/null +++ b/components/categories/CategoryPageContent.tsx @@ -0,0 +1,135 @@ +import React from 'react'; +import { Container, Row } from 'reactstrap'; +import styled, { css } from 'styled-components'; + +import StreamField from 'components/common/StreamField'; +import { GetPlanPageGeneralQuery } from 'common/__generated__/graphql'; +import CategoryPageStreamField, { + checkAttributeHasValueByType, +} from 'components/common/CategoryPageStreamField'; + +type GeneralPlanPage = NonNullable; + +type CategoryPage = { __typename: 'CategoryPage' } & GeneralPlanPage; + +const MainContent = styled.div``; + +const AsideContent = styled.div` + position: sticky; + top: ${({ theme }) => theme.spaces.s200}; + flex: 0 1 300px; + width: 300px; + background-color: ${({ theme }) => theme.themeColors.white}; + border-radius: ${({ theme }) => theme.cardBorderRadius}; + padding: ${({ theme }) => theme.spaces.s100} 0; + display: flex; + flex-direction: column; + align-items: stretch; +`; + +const columnLayout = css` + display: flex; + justify-content: center; + align-items: flex-start; + padding: ${({ theme }) => `0 ${theme.spaces.s200} ${theme.spaces.s200}`}; + gap: ${({ theme }) => theme.spaces.s300}; + + ${MainContent} { + flex: 0 2 800px; + padding: 0 ${({ theme }) => theme.spaces.s100}; + background-color: ${({ theme }) => theme.themeColors.white}; + border-radius: ${({ theme }) => theme.cardBorderRadius}; + } + + @media (max-width: ${({ theme }) => theme.breakpointLg}) { + flex-direction: column-reverse; + justify-content: flex-start; + align-items: stretch; + + ${MainContent}, ${AsideContent} { + position: relative; + top: 0; + flex: 1 0 auto; + width: 100%; + max-width: ${({ theme }) => theme.breakpointMd}; + margin: 0 auto; + } + } + + @media (max-width: ${({ theme }) => theme.breakpointMd}) { + gap: ${({ theme }) => theme.spaces.s150}; + } + + @media (max-width: ${({ theme }) => theme.breakpointSm}) { + padding: ${({ theme }) => `0 ${theme.spaces.s050} ${theme.spaces.s050}`}; + } +`; + +const ContentArea = styled.div<{ + $columnLayout: boolean; + $backgroundColor?: string; +}>` + ${({ $columnLayout }) => $columnLayout && columnLayout}; + background-color: ${({ $backgroundColor }) => $backgroundColor}; +`; + +const CategoryPageContent = ({ + page, + pageSectionColor, +}: { + page: CategoryPage; + pageSectionColor: string; +}) => { + const hasMainContentTemplate = !!page.layout?.layoutMainBottom; + const hasAsideTemplate = !!page.layout?.layoutAside; + const hasAside = + hasAsideTemplate && + page.layout?.layoutAside?.some((block) => + checkAttributeHasValueByType(block.attributeType.identifier, page) + ); + + return ( + + + {hasMainContentTemplate + ? page.layout?.layoutMainBottom?.map((block, i) => ( + + )) + : page.body && ( + + )} + + + {hasAside && ( + + + {page.layout?.layoutAside?.map((block, i) => ( + + + + ))} + + + )} + + ); +}; + +export default CategoryPageContent; diff --git a/components/common/CategoryPageStreamField.tsx b/components/common/CategoryPageStreamField.tsx index 6382a920..da89cff2 100644 --- a/components/common/CategoryPageStreamField.tsx +++ b/components/common/CategoryPageStreamField.tsx @@ -27,10 +27,10 @@ type OmitFields = OmitUnion< interface WrapperProps { children: React.ReactNode; - withContainer: boolean; + withContainer?: boolean; } -const Wrapper = ({ children, withContainer }: WrapperProps) => +const Wrapper = ({ children, withContainer = true }: WrapperProps) => withContainer ? ( {children} @@ -46,34 +46,65 @@ const DEFAULT_COL_PROPS = { className: 'my-4', }; +const TIGHT_COL_PROPS = { + xl: { size: 8, offset: 2 }, + lg: { size: 10, offset: 1 }, + md: { size: 12 }, + className: 'my-4', +}; + interface Props { page: CategoryPage; context?: 'hero' | 'main' | 'aside'; + /** Passed down to reactstrap Col components */ + columnProps?: any; + hasAsideColumn?: boolean; block: | OmitFields | OmitFields; } +const findAttributeByType = (attributeTypeIdentifier: string, page) => + page.category?.attributes?.find( + (attribute) => attribute.type.identifier === attributeTypeIdentifier + ); + +export const checkAttributeHasValueByType = ( + attributeTypeIdentifier: string, + page +) => { + const attribute = findAttributeByType(attributeTypeIdentifier, page); + + return attribute && attributeHasValue(attribute); +}; + export const CategoryPageStreamField = ({ block, page, context = 'main', + columnProps: customColumnProps, + hasAsideColumn = false, }: Props) => { const theme = useTheme(); const plan = usePlan(); + const columnProps = + context === 'main' && hasAsideColumn ? TIGHT_COL_PROPS : DEFAULT_COL_PROPS; switch (block.__typename) { case 'CategoryPageAttributeTypeBlock': { const withContainer = context === 'main'; - const attribute = page.category?.attributes?.find( - (attribute) => - attribute.type.identifier === block.attributeType.identifier + const attribute = findAttributeByType( + block.attributeType.identifier, + page ); if (attribute && attributeHasValue(attribute)) { return ( - + - + + - + @@ -119,7 +150,15 @@ export const CategoryPageStreamField = ({ page.category?.parent?.color || theme.brandLight; - return ; + return ( + + ); } case 'CategoryPageCategoryListBlock': { diff --git a/components/common/StreamField.tsx b/components/common/StreamField.tsx index 5a21c229..40f4b98f 100644 --- a/components/common/StreamField.tsx +++ b/components/common/StreamField.tsx @@ -281,10 +281,11 @@ type StreamFieldBlockProps = { block: StreamFieldFragmentFragment; color: string; hasSidebar: boolean; + columnProps?: any; }; function StreamFieldBlock(props: StreamFieldBlockProps) { - const { id, page, block, color, hasSidebar } = props; + const { id, page, block, color, hasSidebar, columnProps } = props; const { __typename } = block; const plan = useContext(PlanContext); const theme = useTheme(); @@ -304,6 +305,7 @@ function StreamFieldBlock(props: StreamFieldBlockProps) { lg={{ size: 8, offset: hasSidebar ? 4 : 2 }} md={{ size: 10, offset: 1 }} className="my-4" + {...columnProps} > @@ -332,7 +334,7 @@ function StreamFieldBlock(props: StreamFieldBlockProps) { return ( - +
{value}
@@ -456,6 +458,7 @@ function StreamFieldBlock(props: StreamFieldBlockProps) { lg={{ size: 8, offset: hasSidebar ? 4 : 2 }} md={{ size: 10, offset: 1 }} className="my-4" + {...columnProps} >
@@ -506,10 +509,11 @@ interface StreamFieldProps { page: any; blocks: any; hasSidebar?: boolean; + columnProps?: any; } function StreamField(props: StreamFieldProps) { - const { page, blocks, color, hasSidebar = false } = props; + const { page, blocks, color, hasSidebar = false, columnProps } = props; return ( <> @@ -521,6 +525,7 @@ function StreamField(props: StreamFieldProps) { key={block.id} color={color} hasSidebar={hasSidebar} + columnProps={columnProps} /> ))} diff --git a/pages/[...slug].tsx b/pages/[...slug].tsx index e77a6266..1286cb70 100644 --- a/pages/[...slug].tsx +++ b/pages/[...slug].tsx @@ -18,7 +18,7 @@ import ContentPageHeaderBlock from 'components/contentblocks/ContentPageHeaderBl import SecondaryNavigation from 'components/common/SecondaryNavigation'; import { GetPlanPageGeneralQuery } from 'common/__generated__/graphql'; import ActionAttribute from 'components/common/ActionAttribute'; -import CategoryPageStreamField from 'components/common/CategoryPageStreamField'; +import CategoryPageContent from 'components/categories/CategoryPageContent'; const templatedCategoryPageFragment = gql` fragment TemplatedCategoryPageFragment on CategoryPage { @@ -53,6 +53,11 @@ const templatedCategoryPageFragment = gql` } layoutAside { __typename + ... on CategoryPageAttributeTypeBlock { + attributeType { + identifier + } + } } } } @@ -192,6 +197,7 @@ type PageHeaderBlockProps = { page: GeneralPlanPage; color?: string | null; }; + const PageHeaderBlock = ({ color, page }: PageHeaderBlockProps) => { switch (page.__typename) { case 'CategoryPage': { @@ -256,19 +262,17 @@ const Content = ({ page }: { page: GeneralPlanPage }) => { const { title, headerImage } = page; const imageUrl = headerImage?.large.src; const theme = useTheme(); + const isCategoryPage = page.__typename === 'CategoryPage'; const categoryColor = - page.__typename === 'CategoryPage' && - (page.category?.color || page.category?.parent?.color); + isCategoryPage && (page.category?.color || page.category?.parent?.color); const pageSectionColor = categoryColor || theme.brandLight; const hasSecondaryNav = page.parent?.childrenUseSecondaryNavigation ?? false; // Restrict the secondary nav to be shown on StaticPages only currently const siblings = hasSecondaryNav && page.__typename === 'StaticPage' - ? page?.parent?.children + ? page?.parent?.children ?? [] : []; - const hasMainContentTemplate = - page.__typename === 'CategoryPage' && !!page.layout?.layoutMainBottom; return (
@@ -278,41 +282,39 @@ const Content = ({ page }: { page: GeneralPlanPage }) => { description={`${page.searchDescription || title}`} /> -
- {page.leadContent && ( - - - - - - - - )} - {!!siblings && siblings.length > 1 && ( - - )} + {isCategoryPage ? ( + + ) : ( +
+ {page.leadContent && ( + + + + + + + + )} - {hasMainContentTemplate && - page.layout?.layoutMainBottom?.map((block, i) => { - return ( - - ); - })} + {siblings.length > 1 && ( + + )} - {page.body && !hasMainContentTemplate && ( - 1} - /> - )} -
+ {page.body && ( + 1} + /> + )} +
+ )}
); };