diff --git a/apps/portals/elportal/src/pages/HomePageV2.tsx b/apps/portals/elportal/src/pages/HomePageV2.tsx index caeb769cfd7..4b249a4162e 100644 --- a/apps/portals/elportal/src/pages/HomePageV2.tsx +++ b/apps/portals/elportal/src/pages/HomePageV2.tsx @@ -8,6 +8,7 @@ import { PortalHomePageHeader, GoalsV2, PortalFeaturedPartners, + PortalSectionHeader, } from 'synapse-react-client' import ELContributeYourData from '@sage-bionetworks/synapse-portal-framework/components/elportal/ELContributeYourData' import ELGettingStarted from '@sage-bionetworks/synapse-portal-framework/components/elportal/ELGettingStarted' @@ -74,9 +75,12 @@ export default function HomePage() { ({ + [theme.breakpoints.down('md')]: { + display: 'block', + minHeight: '100px', + }, [theme.breakpoints.down('sm')]: { minHeight: '150px', - display: 'block', }, })} > @@ -159,24 +163,25 @@ export default function HomePage() { > -
- - - -
+ + + +
- - Contribute Your Data - - - If you are a funded portal contributor and ready to upload data to the - ELITE Portal, you can begin the data submission process by contacting - our data curation team through our service desk. - - - - + /> @@ -33,7 +33,6 @@ export function IconSquare({ iconUrl, headline, description }) { variant="body1" sx={{ maxWidth: '100%', - color: 'white', fontSize: '13px', }} > @@ -48,59 +47,31 @@ const ELGettingStarted = () => { - - - Getting Started - - - We provide all the help you need for navigating the portal and - accelerating your research. - - - + width: '100%', + borderColor: 'rgba(255, 255, 255, 0.40)', + color: 'primary.contrastText', + }, + '& p': { fontSize: '16px', color: 'primary.contrastText' }, + '.MuiButton-root': { border: '1px solid white' }, + }} + /> { + const theme = useTheme() + return ( - - - Supported by the NIA’s Division of Geriatrics and Clinical Gerontology - (DGCG) - - - + /> { backgroundRepeat: 'no-repeat', backgroundSize: 'contain', }} - > + /> The National Institute on Aging (NIA) is a leading research @@ -77,7 +53,16 @@ const ELSupportedByNIH: React.FC = () => { the healthy, active years of life. As part of the National Institutes of Health (NIH), NIA supports cutting-edge research on aging and age-related diseases, including Alzheimer's disease and other forms of - dementia. With a mission to improve the health and well-being of older + dementia. + + + With a mission to improve the health and well-being of older populations, NIA funds innovative scientific studies, promotes training for the next generation of researchers, and provides trusted health information to the public. By fostering collaboration across diff --git a/packages/synapse-react-client/src/components/FeaturedDataTabs/FacetPlotsCard.tsx b/packages/synapse-react-client/src/components/FeaturedDataTabs/FacetPlotsCard.tsx index 78f30fba56b..d211e9c5ddd 100644 --- a/packages/synapse-react-client/src/components/FeaturedDataTabs/FacetPlotsCard.tsx +++ b/packages/synapse-react-client/src/components/FeaturedDataTabs/FacetPlotsCard.tsx @@ -204,6 +204,9 @@ function FacetPlotsCard(props: FacetPlotsCardProps) { variant={'contained'} href={detailsPagePath} color={'secondary'} + sx={theme => ({ + [theme.breakpoints.down('sm')]: { width: '100%' }, + })} > Explore {selectedFacetValue} diff --git a/packages/synapse-react-client/src/components/FeaturedDataTabs/FeaturedDataTabs.tsx b/packages/synapse-react-client/src/components/FeaturedDataTabs/FeaturedDataTabs.tsx index 16d7bb36a68..ea8c7eb15ea 100644 --- a/packages/synapse-react-client/src/components/FeaturedDataTabs/FeaturedDataTabs.tsx +++ b/packages/synapse-react-client/src/components/FeaturedDataTabs/FeaturedDataTabs.tsx @@ -2,7 +2,7 @@ import { useState } from 'react' import FeaturedDataPlots, { FeaturedDataPlotsProps } from './FeaturedDataPlots' import { Icon } from '../row_renderers/utils' import NoContentAvailable from '../SynapseTable/NoContentAvailable' -import { Button } from '@mui/material' +import { Box, Button } from '@mui/material' import { Paper } from '@mui/material' export type FeatureDataTabProps = { @@ -25,7 +25,10 @@ function FeaturedDataTabs(props: FeaturedDataTabsProps) { // explore all data button const selectedTabProps: FeatureDataTabProps = configs[selectedTabIndex] return ( -
+ {/* tabs */}
{configs.map((config, index) => { @@ -68,6 +71,11 @@ function FeaturedDataTabs(props: FeaturedDataTabsProps) { variant="contained" color="secondary" href={selectedTabProps.explorePagePath} + sx={theme => ({ + [theme.breakpoints.down('sm')]: { + width: '100%', + }, + })} > View All {selectedTabProps.exploreObjectType} @@ -81,7 +89,7 @@ function FeaturedDataTabs(props: FeaturedDataTabsProps) { )} )} -
+
) } diff --git a/packages/synapse-react-client/src/components/FeaturedResearch/FeaturedResearch.tsx b/packages/synapse-react-client/src/components/FeaturedResearch/FeaturedResearch.tsx index 9b8d3069a17..9bb81e75840 100644 --- a/packages/synapse-react-client/src/components/FeaturedResearch/FeaturedResearch.tsx +++ b/packages/synapse-react-client/src/components/FeaturedResearch/FeaturedResearch.tsx @@ -16,6 +16,7 @@ import { formatDate } from '../../utils/functions/DateFormatter' import { useImageUrl } from '../../utils/hooks/useImageUrlUtils' import dayjs from 'dayjs' import { useInView } from 'react-intersection-observer' +import PortalSectionHeader from '../PortalSectionHeader' const transitionTimeoutMs = 400 @@ -240,30 +241,26 @@ function FeaturedResearch(props: FeaturedResearchProps) { /> )} - - - Featured Research - - {remainingCards.map((research, index) => ( - - ))} + + + + {remainingCards.map((research, index) => ( + + ))} + ) diff --git a/packages/synapse-react-client/src/components/GoalsV2/GoalsV2.Desktop.tsx b/packages/synapse-react-client/src/components/GoalsV2/GoalsV2.Desktop.tsx index b5141196bcb..de73c7e9eaa 100644 --- a/packages/synapse-react-client/src/components/GoalsV2/GoalsV2.Desktop.tsx +++ b/packages/synapse-react-client/src/components/GoalsV2/GoalsV2.Desktop.tsx @@ -35,7 +35,10 @@ export default function GoalsV2Desktop({ justifyContent: 'center', }} > - + {countSql && ( )} diff --git a/packages/synapse-react-client/src/components/GoalsV2/GoalsV2.tsx b/packages/synapse-react-client/src/components/GoalsV2/GoalsV2.tsx index c1c84b53d27..c7d287c7b3f 100644 --- a/packages/synapse-react-client/src/components/GoalsV2/GoalsV2.tsx +++ b/packages/synapse-react-client/src/components/GoalsV2/GoalsV2.tsx @@ -6,8 +6,9 @@ import useShowDesktop from '../../utils/hooks/useShowDesktop' import GoalsV2Mobile from './GoalsV2.Mobile' import GoalsV2Desktop from './GoalsV2.Desktop' import { getFieldIndex } from '../../utils/functions/queryUtils' -import { Box, Typography, Button } from '@mui/material' +import { Box, alpha } from '@mui/material' import useGetGoalData from '../../utils/hooks/useGetGoalData' +import PortalSectionHeader from '../PortalSectionHeader' export type GoalsV2Props = { entityId: string @@ -104,45 +105,26 @@ export const GoalsV2: React.FC = (props: GoalsV2Props) => { - - - What's in the Portal? - - - + ({ + h2: { borderColor: alpha(theme.palette.primary.main, 0.2) }, + a: { marginTop: '24px', marginBottom: '30px' }, + })} + /> {goalError && }
{goalsDataArray.map((row, index) => { return showDesktop ? ( - +
- +
) : ( diff --git a/packages/synapse-react-client/src/components/ImageCardGridWithLinks/ImageCardGridWithLinks.tsx b/packages/synapse-react-client/src/components/ImageCardGridWithLinks/ImageCardGridWithLinks.tsx index 76680709839..1ba62bc52fa 100644 --- a/packages/synapse-react-client/src/components/ImageCardGridWithLinks/ImageCardGridWithLinks.tsx +++ b/packages/synapse-react-client/src/components/ImageCardGridWithLinks/ImageCardGridWithLinks.tsx @@ -20,6 +20,7 @@ import useGetQueryResultBundle from '../../synapse-queries/entity/useGetQueryRes import { SynapseConstants } from '../../utils' import { getFieldIndex } from '../../utils/functions/queryUtils' import { parseEntityIdFromSqlStatement } from '../../utils/functions/SqlFunctions' +import PortalSectionHeader from '../PortalSectionHeader' const BORDER_RADIUS = '6px' @@ -178,30 +179,14 @@ function ImageCardGridWithLinks(props: ImageCardGridWithLinksProps) { padding: { xs: '40px', lg: '80px' }, }} > -
- - - {title} - - - {summaryText} - - -
+ {
- - - {title} - - - - {summaryText} - - +
) } diff --git a/packages/synapse-react-client/src/components/PortalFeaturedPartners/PortalFeaturedPartners.tsx b/packages/synapse-react-client/src/components/PortalFeaturedPartners/PortalFeaturedPartners.tsx index cf6ca558785..5c7825819b3 100644 --- a/packages/synapse-react-client/src/components/PortalFeaturedPartners/PortalFeaturedPartners.tsx +++ b/packages/synapse-react-client/src/components/PortalFeaturedPartners/PortalFeaturedPartners.tsx @@ -5,6 +5,7 @@ import { SynapseConstants } from '../../utils' import useGetQueryResultBundle from '../../synapse-queries/entity/useGetQueryResultBundle' import { getFieldIndex } from '../../utils/functions/queryUtils' import { useImageUrl } from '../../utils/hooks/useImageUrlUtils' +import PortalSectionHeader from '../PortalSectionHeader' export type PortalFeaturedPartnersProps = { sql: string @@ -147,28 +148,16 @@ const PortalFeaturedPartners = ({ sql }: PortalFeaturedPartnersProps) => { - - - Our Partners - - + ({ display: 'flex', diff --git a/packages/synapse-react-client/src/components/PortalSectionHeader/PortalSectionHeader.stories.tsx b/packages/synapse-react-client/src/components/PortalSectionHeader/PortalSectionHeader.stories.tsx new file mode 100644 index 00000000000..e89c45853ef --- /dev/null +++ b/packages/synapse-react-client/src/components/PortalSectionHeader/PortalSectionHeader.stories.tsx @@ -0,0 +1,28 @@ +import { Meta, StoryObj } from '@storybook/react' +import { MemoryRouter } from 'react-router' +import PortalSectionHeader from './PortalSectionHeader' + +const meta = { + title: 'Home Page/PortalSectionHeader', + component: PortalSectionHeader, + parameters: { + chromatic: { viewports: [600, 1200] }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Demo: Story = { + render: args => ( + + + + ), + args: { + title: 'Section Title', + summaryText: 'Section summary text.', + buttonText: 'Section button', + link: 'https://example.com', + }, +} diff --git a/packages/synapse-react-client/src/components/PortalSectionHeader/PortalSectionHeader.test.tsx b/packages/synapse-react-client/src/components/PortalSectionHeader/PortalSectionHeader.test.tsx new file mode 100644 index 00000000000..9c9965d544b --- /dev/null +++ b/packages/synapse-react-client/src/components/PortalSectionHeader/PortalSectionHeader.test.tsx @@ -0,0 +1,77 @@ +import { render, screen } from '@testing-library/react' +import { RouterProvider, createMemoryRouter } from 'react-router' +import PortalSectionHeader, { + PortalSectionHeaderProps, +} from './PortalSectionHeader' +import { createWrapper } from '../../testutils/TestingLibraryUtils' + +describe('PortalSectionHeader tests', () => { + const mockProps: PortalSectionHeaderProps = { + title: 'Some Title', + buttonText: 'Some button text', + summaryText: 'Some summary text', + link: '/some-link', + } + + const renderWithRouter = (mockProps: PortalSectionHeaderProps) => { + const router = createMemoryRouter([ + { + path: '/', + element: , + }, + ]) + return render(, { + wrapper: createWrapper(), + }) + } + + it('renders title', () => { + renderWithRouter(mockProps) + expect(screen.getByText('Some Title')).toBeInTheDocument() + }) + + it('renders button when buttonText is provided', () => { + renderWithRouter(mockProps) + expect(screen.getByText('Some button text')).toBeInTheDocument() + }) + + it('renders summary text when provided', () => { + renderWithRouter(mockProps) + expect(screen.getByText('Some summary text')).toBeInTheDocument() + }) + + it('button links to the correct URL', () => { + renderWithRouter(mockProps) + + const button = screen.getByRole('link', { name: 'Some button text' }) + expect(button).toBeInTheDocument() + expect(button).toHaveAttribute('href', mockProps.link) + }) + + test('reverses the order of button and summary text when reverseButtonAndText is true', () => { + const { container } = renderWithRouter({ + ...mockProps, + reverseButtonAndText: true, + }) + + const nestedStack = container.querySelectorAll('div')[2] + expect(nestedStack).toHaveStyle('flex-direction: column-reverse') + const button = nestedStack?.querySelector('a') + const summary = nestedStack?.querySelector('p') + + expect(button).toBeInTheDocument() + expect(summary).toBeInTheDocument() + + expect(button?.textContent).toBe('Some button text') + expect(summary?.textContent).toBe('Some summary text') + }) + + it('applies centered styles when centered prop is true', () => { + const { container } = renderWithRouter({ + ...mockProps, + centered: true, + }) + + expect(container.firstChild).toHaveStyle('width: 100%') + }) +}) diff --git a/packages/synapse-react-client/src/components/PortalSectionHeader/PortalSectionHeader.tsx b/packages/synapse-react-client/src/components/PortalSectionHeader/PortalSectionHeader.tsx new file mode 100644 index 00000000000..682a22bc5a2 --- /dev/null +++ b/packages/synapse-react-client/src/components/PortalSectionHeader/PortalSectionHeader.tsx @@ -0,0 +1,127 @@ +import { + Stack, + Typography, + Button, + SxProps, + Theme, + Box, + Link as MuiLink, +} from '@mui/material' +import { Link as RouterLink } from 'react-router' +import { spreadSx } from '../../theme/spreadSx' + +export type PortalSectionHeaderProps = { + title: string + buttonText?: string + summaryText?: React.ReactNode + link?: string + sx?: SxProps + centered?: boolean + reverseButtonAndText?: boolean +} + +const PortalSectionHeader = ({ + title, + buttonText, + summaryText, + link, + sx, + centered = false, + reverseButtonAndText = false, +}: PortalSectionHeaderProps) => { + const iisExternalLink = + link?.startsWith('http://') || link?.startsWith('https://') + return ( + + + ({ + fontSize: { xs: '24px', md: '32px' }, + borderTop: '4px solid', + borderColor: 'grey.400', + [theme.breakpoints.down('md')]: { + width: '100%', + }, + })} + > + {title} + + {(buttonText || summaryText) && ( + + {buttonText && ( + + )} + {summaryText && ( + + {summaryText} + + )} + + )} + + + ) +} + +export default PortalSectionHeader diff --git a/packages/synapse-react-client/src/components/PortalSectionHeader/index.ts b/packages/synapse-react-client/src/components/PortalSectionHeader/index.ts new file mode 100644 index 00000000000..bd363e8816a --- /dev/null +++ b/packages/synapse-react-client/src/components/PortalSectionHeader/index.ts @@ -0,0 +1,4 @@ +import PortalSectionHeader from './PortalSectionHeader' +import type { PortalSectionHeaderProps } from './PortalSectionHeader' +export { PortalSectionHeader, PortalSectionHeaderProps } +export default PortalSectionHeader diff --git a/packages/synapse-react-client/src/components/RecentPublicationsGrid/RecentPublicationsGrid.tsx b/packages/synapse-react-client/src/components/RecentPublicationsGrid/RecentPublicationsGrid.tsx index 304c4ce8bd1..f5383d16261 100644 --- a/packages/synapse-react-client/src/components/RecentPublicationsGrid/RecentPublicationsGrid.tsx +++ b/packages/synapse-react-client/src/components/RecentPublicationsGrid/RecentPublicationsGrid.tsx @@ -1,19 +1,13 @@ -import { - Box, - Button, - Link as MuiLink, - Skeleton, - Typography, -} from '@mui/material' +import { Box, Link as MuiLink, Skeleton, Typography } from '@mui/material' import Grid from '@mui/material/Unstable_Grid2' import { QueryBundleRequest, Row } from '@sage-bionetworks/synapse-types' import dayjs from 'dayjs' -import { Link } from 'react-router' import useGetQueryResultBundle from '../../synapse-queries/entity/useGetQueryResultBundle' import { SynapseConstants, SynapseUtilityFunctions } from '../../utils' import { formatDate } from '../../utils/functions/DateFormatter' import { getFieldIndex } from '../../utils/functions/queryUtils' import { convertDoiToLink } from '../../utils/functions/RegularExpressions' +import PortalSectionHeader from '../PortalSectionHeader' export type RecentPublicationsGridProps = { sql: string @@ -209,45 +203,18 @@ function RecentPublicationsGrid(props: RecentPublicationsGridProps) { ))} - - + - - Recently Published - - - {summaryText && summaryText} - - {buttonLink && buttonLinkText && ( - - )} - + /> ) diff --git a/packages/synapse-react-client/src/components/index.ts b/packages/synapse-react-client/src/components/index.ts index 825a2121fdb..617a7b4ee12 100644 --- a/packages/synapse-react-client/src/components/index.ts +++ b/packages/synapse-react-client/src/components/index.ts @@ -90,6 +90,7 @@ export * from './PortalFeatureHighlights' export * from './FeaturedResearch' export * from './PortalHomePageHeader' export * from './PortalFeaturedPartners' +export * from './PortalSectionHeader' // TODO: Find a better way to expose Icon components export { Project as ProjectIcon } from '../assets/themed_icons/Project'