aboutsummaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-12-07 18:48:53 +0100
committerArmand Philippot <git@armandphilippot.com>2023-12-08 19:13:47 +0100
commitd375e5c9f162cbd84a6e6462977db56519d09f75 (patch)
treeaed9bc81c426e3e9fb60292cb244613cb8083dea /src/components
parentb8eb008dd5927fb736e56699637f5f8549965eae (diff)
refactor(pages): refine Project pages
* refactor ProjectOverview component to let consumers handle the value * extract project overview depending on Github to avoid fetching Github API if the project is not on Github * wrap dynamic import in a useMemo hook to avoid infinite rerender * fix table of contents by adding a useMutationObserver hook to refresh headings tree (without it useHeadingsTree is not retriggered once the dynamic import is done) * add Cypress tests
Diffstat (limited to 'src/components')
-rw-r--r--src/components/molecules/meta-list/meta-item/meta-item.tsx3
-rw-r--r--src/components/organisms/project-overview/project-overview.stories.tsx4
-rw-r--r--src/components/organisms/project-overview/project-overview.test.tsx64
-rw-r--r--src/components/organisms/project-overview/project-overview.tsx93
4 files changed, 56 insertions, 108 deletions
diff --git a/src/components/molecules/meta-list/meta-item/meta-item.tsx b/src/components/molecules/meta-list/meta-item/meta-item.tsx
index c5223c2..42a0801 100644
--- a/src/components/molecules/meta-list/meta-item/meta-item.tsx
+++ b/src/components/molecules/meta-list/meta-item/meta-item.tsx
@@ -1,13 +1,12 @@
import {
type ForwardRefRenderFunction,
- type ReactElement,
type ReactNode,
forwardRef,
} from 'react';
import { Description, Group, type GroupProps, Term } from '../../../atoms';
import styles from './meta-item.module.scss';
-export type MetaValue = string | ReactElement;
+export type MetaValue = ReactNode;
export type MetaValues = {
id: string;
diff --git a/src/components/organisms/project-overview/project-overview.stories.tsx b/src/components/organisms/project-overview/project-overview.stories.tsx
index 655dc3c..0c4138c 100644
--- a/src/components/organisms/project-overview/project-overview.stories.tsx
+++ b/src/components/organisms/project-overview/project-overview.stories.tsx
@@ -1,6 +1,6 @@
import type { ComponentMeta, ComponentStory } from '@storybook/react';
import NextImage from 'next/image';
-import { type ProjectMeta, ProjectOverview } from './project-overview';
+import { type OverviewMeta, ProjectOverview } from './project-overview';
/**
* ProjectOverview - Storybook Meta
@@ -49,7 +49,7 @@ const meta = {
creationDate: '2015-09-02',
lastUpdateDate: '2023-11-10',
license: 'MIT',
-} satisfies Partial<ProjectMeta>;
+} satisfies Partial<OverviewMeta>;
/**
* ProjectOverview Stories - Meta
diff --git a/src/components/organisms/project-overview/project-overview.test.tsx b/src/components/organisms/project-overview/project-overview.test.tsx
index 6234368..f798a7b 100644
--- a/src/components/organisms/project-overview/project-overview.test.tsx
+++ b/src/components/organisms/project-overview/project-overview.test.tsx
@@ -1,13 +1,14 @@
import { describe, expect, it } from '@jest/globals';
import NextImage from 'next/image';
import { render, screen as rtlScreen } from '../../../../tests/utils';
-import { type ProjectMeta, ProjectOverview } from './project-overview';
+import { type OverviewMeta, ProjectOverview } from './project-overview';
+import { SocialLink } from 'src/components/atoms';
describe('ProjectOverview', () => {
it('can render a meta for the creation date', () => {
const meta = {
creationDate: '2023-11-01',
- } satisfies Partial<ProjectMeta>;
+ } satisfies Partial<OverviewMeta>;
render(<ProjectOverview meta={meta} name="quo" />);
@@ -17,7 +18,7 @@ describe('ProjectOverview', () => {
it('can render a meta for the update date', () => {
const meta = {
lastUpdateDate: '2023-11-02',
- } satisfies Partial<ProjectMeta>;
+ } satisfies Partial<OverviewMeta>;
render(<ProjectOverview meta={meta} name="quo" />);
@@ -27,7 +28,7 @@ describe('ProjectOverview', () => {
it('can render a meta for the license', () => {
const meta = {
license: 'MIT',
- } satisfies Partial<ProjectMeta>;
+ } satisfies Partial<OverviewMeta>;
render(<ProjectOverview meta={meta} name="quo" />);
@@ -37,38 +38,26 @@ describe('ProjectOverview', () => {
it('can render a meta for the popularity', () => {
const meta = {
- popularity: { count: 5 },
- } satisfies Partial<ProjectMeta>;
+ popularity: '5 stars',
+ } satisfies Partial<OverviewMeta>;
render(<ProjectOverview meta={meta} name="quo" />);
expect(rtlScreen.getByRole('term')).toHaveTextContent('Popularity:');
expect(rtlScreen.getByRole('definition')).toHaveTextContent(
- `${meta.popularity.count} stars`
- );
- });
-
- it('can render a meta for the popularity with a link', () => {
- const meta = {
- popularity: { count: 3, url: '#popularity' },
- } satisfies Partial<ProjectMeta>;
-
- render(<ProjectOverview meta={meta} name="quo" />);
-
- expect(rtlScreen.getByRole('term')).toHaveTextContent('Popularity:');
- expect(rtlScreen.getByRole('definition')).toHaveTextContent(
- `${meta.popularity.count} stars`
- );
- expect(rtlScreen.getByRole('link')).toHaveAttribute(
- 'href',
- meta.popularity.url
+ meta.popularity
);
});
it('can render a meta for the technologies', () => {
const meta = {
- technologies: ['Javascript', 'React'],
- } satisfies Partial<ProjectMeta>;
+ technologies: ['Javascript', 'React'].map((techno) => {
+ return {
+ id: techno,
+ value: techno,
+ };
+ }),
+ } satisfies Partial<OverviewMeta>;
render(<ProjectOverview meta={meta} name="quo" />);
@@ -79,9 +68,22 @@ describe('ProjectOverview', () => {
});
it('can render a meta for the repositories', () => {
+ const repos = [{ id: 'Github' as const, label: 'Github', url: '#github' }];
const meta = {
- repositories: [{ id: 'Github', label: 'Github', url: '#github' }],
- } satisfies Partial<ProjectMeta>;
+ repositories: repos.map((repo) => {
+ return {
+ id: repo.id,
+ value: (
+ <SocialLink
+ icon={repo.id}
+ key={repo.id}
+ label={repo.label}
+ url={repo.url}
+ />
+ ),
+ };
+ }),
+ } satisfies Partial<OverviewMeta>;
render(<ProjectOverview meta={meta} name="quo" />);
@@ -90,8 +92,8 @@ describe('ProjectOverview', () => {
meta.repositories.length
);
expect(
- rtlScreen.getByRole('link', { name: meta.repositories[0].label })
- ).toHaveAttribute('href', meta.repositories[0].url);
+ rtlScreen.getByRole('link', { name: repos[0].label })
+ ).toHaveAttribute('href', repos[0].url);
});
it('can render a cover', () => {
@@ -118,7 +120,7 @@ describe('ProjectOverview', () => {
it('does not render a meta if the key is undefined', () => {
const meta = {
creationDate: undefined,
- } satisfies Partial<ProjectMeta>;
+ } satisfies Partial<OverviewMeta>;
render(<ProjectOverview meta={meta} name="quo" />);
diff --git a/src/components/organisms/project-overview/project-overview.tsx b/src/components/organisms/project-overview/project-overview.tsx
index f524120..d7416ec 100644
--- a/src/components/organisms/project-overview/project-overview.tsx
+++ b/src/components/organisms/project-overview/project-overview.tsx
@@ -6,35 +6,23 @@ import {
type ReactElement,
} from 'react';
import { useIntl } from 'react-intl';
-import type { ValueOf } from '../../../types';
+import { Figure } from '../../atoms';
import {
- Time,
- type SocialWebsite,
- Link,
- SocialLink,
- Figure,
-} from '../../atoms';
-import { MetaItem, type MetaItemProps, MetaList } from '../../molecules';
+ MetaItem,
+ type MetaItemProps,
+ MetaList,
+ type MetaValue,
+ type MetaValues,
+} from '../../molecules';
import styles from './project-overview.module.scss';
-export type Repository = {
- id: Extract<SocialWebsite, 'Github' | 'Gitlab'>;
- label: string;
- url: string;
-};
-
-export type ProjectPopularity = {
- count: number;
- url?: string;
-};
-
-export type ProjectMeta = {
- creationDate: string;
- lastUpdateDate: string;
- license: string;
- popularity: ProjectPopularity;
- repositories: Repository[];
- technologies: string[];
+export type OverviewMeta = {
+ creationDate: MetaValue;
+ lastUpdateDate: MetaValue;
+ license: MetaValue;
+ popularity: MetaValue;
+ repositories: MetaValue | MetaValues[];
+ technologies: MetaValues[];
};
const validMeta = [
@@ -44,9 +32,9 @@ const validMeta = [
'popularity',
'repositories',
'technologies',
-] satisfies (keyof ProjectMeta)[];
+] satisfies (keyof OverviewMeta)[];
-const isValidMetaKey = (key: string): key is keyof ProjectMeta =>
+const isValidMetaKey = (key: string): key is keyof OverviewMeta =>
(validMeta as string[]).includes(key);
export type ProjectOverviewProps = Omit<
@@ -60,7 +48,7 @@ export type ProjectOverviewProps = Omit<
/**
* The project meta.
*/
- meta: Partial<ProjectMeta>;
+ meta: Partial<OverviewMeta>;
/**
* The project name.
*/
@@ -112,48 +100,7 @@ const ProjectOverviewWithRef: ForwardRefRenderFunction<
description: 'ProjectOverview: technologies label',
id: 'OWkqXt',
}),
- } satisfies Record<keyof ProjectMeta, string>;
-
- const getMetaValue = useCallback(
- (key: keyof ProjectMeta, value: ValueOf<ProjectMeta>) => {
- if (typeof value === 'string') {
- return key === 'license' ? value : <Time date={value} />;
- }
-
- if (
- (value instanceof Object || typeof value === 'object') &&
- !Array.isArray(value)
- ) {
- const stars = intl.formatMessage(
- {
- defaultMessage:
- '{starsCount, plural, =0 {No stars} one {# star} other {# stars}}',
- description: 'ProjectOverview: stars count',
- id: 'PBdVsm',
- },
- { starsCount: value.count }
- );
-
- return value.url ? (
- <>
- ⭐&nbsp;<Link href={value.url}>{stars}</Link>
- </>
- ) : (
- `⭐\u00A0${stars}`
- );
- }
-
- return value.map((v) => {
- if (typeof v === 'string') return { id: v, value: v };
-
- return {
- id: v.id,
- value: <SocialLink icon={v.id} label={v.label} url={v.url} />,
- };
- });
- },
- [intl]
- );
+ } satisfies Record<keyof OverviewMeta, string>;
const getMetaItems = useCallback(() => {
const keys = Object.keys(meta).filter(isValidMetaKey);
@@ -172,7 +119,7 @@ const ProjectOverviewWithRef: ForwardRefRenderFunction<
}
key={key}
label={metaLabels[key]}
- value={getMetaValue(key, value)}
+ value={value}
/>
) : undefined;
})
@@ -180,7 +127,7 @@ const ProjectOverviewWithRef: ForwardRefRenderFunction<
(item): item is ReactElement<MetaItemProps> =>
typeof item !== 'undefined'
);
- }, [getMetaValue, meta, metaLabels]);
+ }, [meta, metaLabels]);
return (
<div {...props} className={wrapperClass} ref={ref}>