diff --git a/package.json b/package.json index c13c38f9..821e38f4 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@markdoc/markdoc": "0.4.0", "@markdoc/next.js": "0.2.2", "@open-draft/until": "2.1.0", - "@pluralsh/design-system": "3.49.0", + "@pluralsh/design-system": "3.52.1", "@react-types/shared": "3.22.0", "@tanstack/react-table": "8.10.7", "@tanstack/react-virtual": "3.0.1", diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx index 9bf7daf6..01b5fa09 100644 --- a/src/components/Navigation.tsx +++ b/src/components/Navigation.tsx @@ -64,6 +64,63 @@ export const ProductLink = forwardRef( } ) +export const SolutionLink = forwardRef( + (props: ComponentProps, ref) => { + const { Link } = useNavigationContext() + + return ( + + + {props.children} + + + + ) + } +) +export const ProductMobileLink = forwardRef( + (props: ComponentProps, ref) => { + const { Link } = useNavigationContext() + + const itemConfig = getProductsConfigs()[props.id || ''] + + return ( + +
+ {itemConfig?.navIcon} +
+ + {itemConfig?.title} + + + +
+ ) + } +) + export const MainLinkBase = styled.a.withConfig({ shouldForwardProp: (prop) => !['isDisabled', 'isSelected'].includes(prop), })<{ isDisabled?: boolean; isSelected?: boolean }>(({ theme, isSelected }) => ({ diff --git a/src/components/NavigationDesktop.tsx b/src/components/NavigationDesktop.tsx index 3db06a9a..f61791a2 100644 --- a/src/components/NavigationDesktop.tsx +++ b/src/components/NavigationDesktop.tsx @@ -9,6 +9,7 @@ import { type NavData, useNavData } from '@src/contexts/NavDataContext' import { mqs } from '../breakpoints' import { ProductTopNavMenu } from './menu/ProductNav' +import { SolutionTopNavMenu } from './menu/SolutionNav' import { TopNavMenu } from './menu/TopNavMenu' import { MainLink } from './Navigation' @@ -53,6 +54,14 @@ export const NavigationDesktop = styled( /> ) } + if (i === 1) { + return ( + + ) + } return ( ({ - paddingLeft: 0, - paddingRight: 0, - paddingTop: theme.spacing.xsmall, - paddingBottom: theme.spacing.xsmall, - marginBottom: theme.spacing.xsmall, +const MobileMainLink = styled(MainLink)(() => ({ width: '100%', + marginTop: 1, })) export const MenuHeading = styled.h6(({ theme }) => ({ @@ -34,12 +32,6 @@ type MobileMenuProps = NavContextValue & { className?: string } -const SocialIcons = styled.div(({ theme }) => ({ - display: 'flex', - marginTop: theme.spacing.xlarge, - gap: theme.spacing.medium, -})) - type NavData = ( | (NavListFragment & { subnav?: NavData | null @@ -48,40 +40,79 @@ type NavData = ( )[] function NavList({ navData }: { navData?: NavData | null }) { + const theme = useTheme() + if (!navData) { return null } return ( - <> +
{navData.map((navItem) => { if (!navItem) { return null } if (isEmpty(navItem?.subnav)) { return ( - } + style={{ + justifyContent: 'space-between', + padding: theme.spacing.medium, + borderColor: theme.colors.border, + }} > {navItem?.link?.title} - + ) } return ( -
- {navItem?.link?.title ? ( - {navItem?.link?.title} - ) : null} - -
+ {navItem.subnav?.map((subnavItem) => { + if (!subnavItem) { + return null + } + + if (navItem.link?.title === 'Product') { + return ( + + {subnavItem?.link?.title} + + ) + } + + return ( + + {subnavItem?.link?.title} + + ) + })} + ) })} - +
) } @@ -93,21 +124,44 @@ function PluralMenuContent({ className?: string }) { const navData = useNavData() + const theme = useTheme() return (
- - - + + +
+ + ) } diff --git a/src/components/menu/Menu.tsx b/src/components/menu/Menu.tsx index a0aeae62..f78b4f26 100644 --- a/src/components/menu/Menu.tsx +++ b/src/components/menu/Menu.tsx @@ -28,6 +28,7 @@ import { import styled from 'styled-components' import { MainLinkBase } from '../Navigation' +import { ResponsiveText } from '../Typography' import { PopoverMenu } from './PopoverMenu' @@ -106,7 +107,10 @@ export function MenuButton({ kind = 'default', left, ...props -}: MenuButtonProps & { kind?: 'product' | 'default'; left?: number }) { +}: MenuButtonProps & { + kind?: 'product' | 'default' | 'solution' + left?: number +}) { // Create state based on the incoming props const triggerState = useMenuTriggerState(props) @@ -145,13 +149,22 @@ export function MenuButton({ onClose={triggerState.close} floating={floating} > - - {children} - + {kind === 'solution' ? ( + + {children} + + ) : ( + + {children} + + )} ) @@ -167,7 +180,7 @@ function MenuDropdown({ ...props }: AriaMenuProps & { itemRenderer?: ItemRenderer - kind?: 'product' | 'default' + kind?: 'product' | 'default' | 'solution' }) { // Create menu state based on the incoming props const state = useTreeState(props) @@ -200,6 +213,72 @@ function MenuDropdown({ ) } +function SolutionNavDropdown({ + itemRenderer = MenuItem, + ...props +}: AriaMenuProps & { + itemRenderer?: ItemRenderer +}) { + // Create menu state based on the incoming props + const state = useTreeState(props) + + const collection = [...state.collection] + + const itemByCategory = collection.reduce((acc, item) => { + // @ts-ignore + const category = item?.value?.link?.category + + if (!category) return acc + + if (!acc[category]) { + acc[category] = [] + } + + acc[category].push(item) + + return acc + }, {}) + + // Get props for the menu element + const ref = React.useRef(null) + const { menuProps } = useMenu(props, state, ref) + + const ItemRenderer = itemRenderer + + return ( +
+ +
+ {Object.keys(itemByCategory).map((category) => ( +
+ + {category} + +
    + {itemByCategory[category].map((item) => ( + + ))} +
+
+ ))} +
+
+
+ ) +} + const DropdownCardSC = styled.div(({ theme }) => ({ overflowX: 'hidden', overflowY: 'auto', diff --git a/src/components/menu/SolutionNav.tsx b/src/components/menu/SolutionNav.tsx new file mode 100644 index 00000000..96a1233f --- /dev/null +++ b/src/components/menu/SolutionNav.tsx @@ -0,0 +1,79 @@ +import React, { useMemo } from 'react' + +import { useNavigationContext } from '@pluralsh/design-system' + +import { useMenuItem } from 'react-aria' +import { Item } from 'react-stately' +import styled, { useTheme } from 'styled-components' + +import { type NavList } from '@src/contexts/NavDataContext' + +import { SolutionLink } from '../Navigation' + +import { type ItemRendererProps, MenuButton } from './Menu' + +function SolutionTopNavMenuItem({ + item, + state, +}: ItemRendererProps) { + // Get props for the menu item element + const ref = React.useRef(null) + const { menuItemProps, isSelected, isDisabled } = useMenuItem( + { key: item.key }, + state, + ref + ) + + return ( + + + {item.rendered} + + + ) +} + +const TopNavMenuItemWrapper = styled.li((_) => ({})) + +export function SolutionTopNavMenu({ navItem }: { navItem: NavList }) { + const navigate = useNavigationContext().useNavigate() + const theme = useTheme() + const items = useMemo( + () => navItem?.subnav?.filter((item): item is NavList => !!item), + [navItem.subnav] + ) + + if (!items) { + return null + } + + return ( + { + const item = navItem.subnav?.find((item) => item?.id === key) + const url = item?.link?.url + + if (url) { + navigate(url) + } + }} + > + {({ ...item }) => ( + {item.link?.title} + )} + + ) +} diff --git a/src/data/getSiteSettings.tsx b/src/data/getSiteSettings.tsx index 485d6d8c..f93c68ef 100644 --- a/src/data/getSiteSettings.tsx +++ b/src/data/getSiteSettings.tsx @@ -2,7 +2,8 @@ import { getProductsConfigs } from './getProductConfigs' type Solution = { slug?: string | null - title?: string | null + nav_title?: string | null + category?: string | null } export const getSiteSettings = (solutions?: Solution[]) => ({ @@ -221,8 +222,9 @@ function getSolutionSubnav(solutions?: Solution[]) { id: solution.slug || '', link: { id: `${solution.slug}-${i}`, - title: solution.title, + title: solution.nav_title, url: `/solutions/${solution.slug}`, + category: solution.category, }, })) } diff --git a/src/generated/graphqlDirectus.ts b/src/generated/graphqlDirectus.ts index 7abaecda..3d9f25ad 100644 --- a/src/generated/graphqlDirectus.ts +++ b/src/generated/graphqlDirectus.ts @@ -4721,6 +4721,7 @@ export type Create_Page_Community_Input = { export type Create_Page_Homepage_Input = { article_cards?: InputMaybe>>; + case_study?: InputMaybe>>; date_created?: InputMaybe; date_updated?: InputMaybe; featured_quote?: InputMaybe; @@ -4875,6 +4876,7 @@ export type Create_Solution_Problems_Input = { }; export type Create_Solutions_Pages_Input = { + category?: InputMaybe; date_created?: InputMaybe; date_updated?: InputMaybe; description?: InputMaybe; @@ -4884,6 +4886,7 @@ export type Create_Solutions_Pages_Input = { id?: InputMaybe; lower_features?: InputMaybe>>; lower_features_title?: InputMaybe; + nav_title?: InputMaybe; problems?: InputMaybe>>; slug: Scalars['String']['input']; sort?: InputMaybe; @@ -5953,6 +5956,7 @@ export type Page_Homepage = { __typename?: 'page_homepage'; article_cards?: Maybe>>; article_cards_func?: Maybe; + case_study?: Maybe>>; date_created?: Maybe; date_created_func?: Maybe; date_updated?: Maybe; @@ -5975,6 +5979,16 @@ export type Page_HomepageArticle_CardsArgs = { }; +export type Page_HomepageCase_StudyArgs = { + filter?: InputMaybe; + limit?: InputMaybe; + offset?: InputMaybe; + page?: InputMaybe; + search?: InputMaybe; + sort?: InputMaybe>>; +}; + + export type Page_HomepageFeatured_QuoteArgs = { filter?: InputMaybe; limit?: InputMaybe; @@ -6019,6 +6033,7 @@ export type Page_Homepage_Filter = { _or?: InputMaybe>>; article_cards?: InputMaybe; article_cards_func?: InputMaybe; + case_study?: InputMaybe; date_created?: InputMaybe; date_created_func?: InputMaybe; date_updated?: InputMaybe; @@ -7196,6 +7211,7 @@ export type Solution_Problems_Filter = { export type Solutions_Pages = { __typename?: 'solutions_pages'; + category?: Maybe; date_created?: Maybe; date_created_func?: Maybe; date_updated?: Maybe; @@ -7208,6 +7224,7 @@ export type Solutions_Pages = { lower_features?: Maybe>>; lower_features_func?: Maybe; lower_features_title?: Maybe; + nav_title?: Maybe; problems?: Maybe>>; problems_func?: Maybe; slug: Scalars['String']['output']; @@ -7297,6 +7314,7 @@ export type Solutions_Pages_Aggregated = { export type Solutions_Pages_Aggregated_Count = { __typename?: 'solutions_pages_aggregated_count'; + category?: Maybe; date_created?: Maybe; date_updated?: Maybe; description?: Maybe; @@ -7306,6 +7324,7 @@ export type Solutions_Pages_Aggregated_Count = { id?: Maybe; lower_features?: Maybe; lower_features_title?: Maybe; + nav_title?: Maybe; problems?: Maybe; slug?: Maybe; sort?: Maybe; @@ -7327,6 +7346,7 @@ export type Solutions_Pages_Aggregated_Fields = { export type Solutions_Pages_Filter = { _and?: InputMaybe>>; _or?: InputMaybe>>; + category?: InputMaybe; date_created?: InputMaybe; date_created_func?: InputMaybe; date_updated?: InputMaybe; @@ -7339,6 +7359,7 @@ export type Solutions_Pages_Filter = { lower_features?: InputMaybe; lower_features_func?: InputMaybe; lower_features_title?: InputMaybe; + nav_title?: InputMaybe; problems?: InputMaybe; problems_func?: InputMaybe; slug?: InputMaybe; @@ -7967,6 +7988,7 @@ export type Update_Page_Community_Input = { export type Update_Page_Homepage_Input = { article_cards?: InputMaybe>>; + case_study?: InputMaybe>>; date_created?: InputMaybe; date_updated?: InputMaybe; featured_quote?: InputMaybe; @@ -8131,6 +8153,7 @@ export type Update_Solution_Problems_Input = { }; export type Update_Solutions_Pages_Input = { + category?: InputMaybe; date_created?: InputMaybe; date_updated?: InputMaybe; description?: InputMaybe; @@ -8140,6 +8163,7 @@ export type Update_Solutions_Pages_Input = { id?: InputMaybe; lower_features?: InputMaybe>>; lower_features_title?: InputMaybe; + nav_title?: InputMaybe; problems?: InputMaybe>>; slug?: InputMaybe; sort?: InputMaybe; @@ -8263,7 +8287,7 @@ export type SolutionFragment = { __typename?: 'solutions_pages', id: string, slu export type SolutionsSlugsQueryVariables = Exact<{ [key: string]: never; }>; -export type SolutionsSlugsQuery = { __typename?: 'Query', solutions_pages: Array<{ __typename?: 'solutions_pages', slug: string, title?: string | null }> }; +export type SolutionsSlugsQuery = { __typename?: 'Query', solutions_pages: Array<{ __typename?: 'solutions_pages', slug: string, nav_title?: string | null, category?: string | null }> }; export type SolutionsQueryVariables = Exact<{ slug?: InputMaybe; @@ -8881,7 +8905,8 @@ export const SolutionsSlugsDocument = gql` query SolutionsSlugs { solutions_pages { slug - title + nav_title + category } } `; diff --git a/src/graph/directus/cms.graphql b/src/graph/directus/cms.graphql index 94eca9a0..b2319639 100644 --- a/src/graph/directus/cms.graphql +++ b/src/graph/directus/cms.graphql @@ -273,7 +273,8 @@ fragment Solution on solutions_pages { query SolutionsSlugs { solutions_pages { slug - title + nav_title + category } } diff --git a/yarn.lock b/yarn.lock index 6faf5247..bb8d8240 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4276,9 +4276,9 @@ __metadata: languageName: node linkType: hard -"@pluralsh/design-system@npm:3.49.0": - version: 3.49.0 - resolution: "@pluralsh/design-system@npm:3.49.0" +"@pluralsh/design-system@npm:3.52.1": + version: 3.52.1 + resolution: "@pluralsh/design-system@npm:3.52.1" dependencies: "@floating-ui/react-dom-interactions": 0.13.3 "@loomhq/loom-embed": 1.5.0 @@ -4326,7 +4326,7 @@ __metadata: react-dom: ">=18.3.1" react-transition-group: ">=4.4.5" styled-components: ">=5.3.11" - checksum: 17b233f9eb16f2a94c2b5197d3ac12ee529fa65c298b092d43f4aec21bc3749cdf03953d9421bfb737fe728cb1993e5d1467b12f4d0373f0382be29a8770d123 + checksum: 1e802c690394138e35696e2e2e7b49d4af0a61bdabc27474118c65df2d0326b25031132906a0046d9e9b847a6ef99f3f9e6e8e4f4d38ea68af97eb1cc1d91969 languageName: node linkType: hard @@ -13743,7 +13743,7 @@ __metadata: "@markdoc/next.js": 0.2.2 "@next/bundle-analyzer": 13.4.12 "@open-draft/until": 2.1.0 - "@pluralsh/design-system": 3.49.0 + "@pluralsh/design-system": 3.52.1 "@pluralsh/eslint-config-typescript": 2.5.183 "@pluralsh/stylelint-config": 2.0.10 "@radix-ui/react-slot": 1.0.2