From d9bf6f0d69ecb4475c06c772ef6314e5a7ee0fe8 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Fri, 15 Apr 2022 16:32:55 +0200 Subject: chore: add a LinksListWidget component --- .../widgets/links-list-widget.module.scss | 71 +++++++++++++++++ .../widgets/links-list-widget.stories.tsx | 92 ++++++++++++++++++++++ .../organisms/widgets/links-list-widget.test.tsx | 32 ++++++++ .../organisms/widgets/links-list-widget.tsx | 81 +++++++++++++++++++ 4 files changed, 276 insertions(+) create mode 100644 src/components/organisms/widgets/links-list-widget.module.scss create mode 100644 src/components/organisms/widgets/links-list-widget.stories.tsx create mode 100644 src/components/organisms/widgets/links-list-widget.test.tsx create mode 100644 src/components/organisms/widgets/links-list-widget.tsx (limited to 'src/components/organisms/widgets') diff --git a/src/components/organisms/widgets/links-list-widget.module.scss b/src/components/organisms/widgets/links-list-widget.module.scss new file mode 100644 index 0000000..cbad83e --- /dev/null +++ b/src/components/organisms/widgets/links-list-widget.module.scss @@ -0,0 +1,71 @@ +@use "@styles/abstracts/functions" as fun; +@use "@styles/abstracts/placeholders"; + +.widget { + .list { + &__link { + display: block; + padding: var(--spacing-2xs) var(--spacing-xs); + background: none; + text-decoration: underline solid transparent 0; + + &:hover, + &:focus { + background: var(--color-bg-secondary); + font-weight: 600; + } + + &:focus { + color: var(--color-primary); + text-decoration-color: var(--color-primary-light); + text-decoration-thickness: 0.25ex; + } + + &:active { + background: var(--color-bg-tertiary); + text-decoration-color: transparent; + text-decoration-thickness: 0; + } + } + + &--ordered { + @extend %reset-ordered-list; + + counter-reset: link; + + .list__link { + counter-increment: link; + + &::before { + padding-right: var(--spacing-2xs); + content: counters(link, ".") ". "; + color: var(--color-secondary); + } + } + } + + &--unordered { + @extend %reset-list; + } + + &__item { + &:not(:last-child) { + .list__link { + border-bottom: fun.convert-px(1) solid var(--color-primary); + } + } + + > .list { + .list__link { + padding-left: var(--spacing-md); + } + + .list__item > .list { + .list__link { + padding-left: var(--spacing-xl); + } + } + } + } + } +} diff --git a/src/components/organisms/widgets/links-list-widget.stories.tsx b/src/components/organisms/widgets/links-list-widget.stories.tsx new file mode 100644 index 0000000..528f6f7 --- /dev/null +++ b/src/components/organisms/widgets/links-list-widget.stories.tsx @@ -0,0 +1,92 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { IntlProvider } from 'react-intl'; +import LinksListWidget from './links-list-widget'; + +export default { + title: 'Organisms/Widgets', + component: LinksListWidget, + args: { + kind: 'unordered', + }, + argTypes: { + items: { + description: 'The widget data.', + type: { + name: 'object', + required: true, + value: {}, + }, + }, + kind: { + control: { + type: 'select', + }, + description: 'The list kind: either ordered or unordered.', + options: ['ordered', 'unordered'], + table: { + category: 'Options', + defaultValue: { summary: 'unordered' }, + }, + type: { + name: 'string', + required: false, + }, + }, + level: { + control: { + type: 'number', + }, + description: 'The heading level.', + type: { + name: 'number', + required: true, + }, + }, + title: { + control: { + type: 'text', + }, + description: 'The widget title.', + type: { + name: 'string', + required: true, + }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + + + +); + +const items = [ + { name: 'Level 1: Item 1', url: '#' }, + { + name: 'Level 1: Item 2', + url: '#', + child: [ + { name: 'Level 2: Item 1', url: '#' }, + { name: 'Level 2: Item 2', url: '#' }, + { + name: 'Level 2: Item 3', + url: '#', + child: [ + { name: 'Level 3: Item 1', url: '#' }, + { name: 'Level 3: Item 2', url: '#' }, + ], + }, + { name: 'Level 2: Item 4', url: '#' }, + ], + }, + { name: 'Level 1: Item 3', url: '#' }, + { name: 'Level 1: Item 4', url: '#' }, +]; + +export const LinksList = Template.bind({}); +LinksList.args = { + items, + level: 2, + title: 'A list of links', +}; diff --git a/src/components/organisms/widgets/links-list-widget.test.tsx b/src/components/organisms/widgets/links-list-widget.test.tsx new file mode 100644 index 0000000..a8d6a35 --- /dev/null +++ b/src/components/organisms/widgets/links-list-widget.test.tsx @@ -0,0 +1,32 @@ +import { render, screen } from '@test-utils'; +import LinksListWidget from './links-list-widget'; + +const title = 'Voluptatem minus autem'; + +const items = [ + { name: 'Item 1', url: '/item-1' }, + { name: 'Item 2', url: '/item-2' }, + { name: 'Item 3', url: '/item-3' }, +]; + +describe('LinksListWidget', () => { + it('renders a widget title', () => { + render(); + expect( + screen.getByRole('heading', { level: 2, name: new RegExp(title, 'i') }) + ).toBeInTheDocument(); + }); + + it('renders the correct number of items', () => { + render(); + expect(screen.getAllByRole('listitem')).toHaveLength(items.length); + }); + + it('renders some links', () => { + render(); + expect(screen.getByRole('link', { name: items[0].name })).toHaveAttribute( + 'href', + items[0].url + ); + }); +}); diff --git a/src/components/organisms/widgets/links-list-widget.tsx b/src/components/organisms/widgets/links-list-widget.tsx new file mode 100644 index 0000000..155354e --- /dev/null +++ b/src/components/organisms/widgets/links-list-widget.tsx @@ -0,0 +1,81 @@ +import Link from '@components/atoms/links/link'; +import List, { ListProps, type ListItem } from '@components/atoms/lists/list'; +import Widget, { type WidgetProps } from '@components/molecules/layout/widget'; +import { slugify } from '@utils/helpers/slugify'; +import { VFC } from 'react'; +import styles from './links-list-widget.module.scss'; + +export type LinksListItems = { + /** + * An array of name/url couple child of this list item. + */ + child?: LinksListItems[]; + /** + * The item name. + */ + name: string; + /** + * The item url. + */ + url: string; +}; + +export type LinksListWidgetProps = Pick & + Pick & { + /** + * An array of name/url couple. + */ + items: LinksListItems[]; + }; + +/** + * LinksListWidget component + * + * Render a list of links inside a widget. + */ +const LinksListWidget: VFC = ({ + items, + kind = 'unordered', + ...props +}) => { + const listKindClass = `list--${kind}`; + + /** + * Format the widget data to be used as List items. + * + * @param {LinksListItems[]} data - The widget data. + * @returns {ListItem[]} The list items data. + */ + const getListItems = (data: LinksListItems[]): ListItem[] => { + return data.map((item) => { + return { + id: slugify(item.name), + child: item.child && getListItems(item.child), + value: ( + + {item.name} + + ), + }; + }); + }; + + return ( + + + + ); +}; + +export default LinksListWidget; -- cgit v1.2.3 From a07729064790df13324dbe7f4d1629892070558b Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Fri, 15 Apr 2022 17:13:21 +0200 Subject: chore: add a SharingWidget component --- .../organisms/widgets/sharing.module.scss | 10 ++ .../organisms/widgets/sharing.stories.tsx | 78 +++++++++ src/components/organisms/widgets/sharing.test.tsx | 31 ++++ src/components/organisms/widgets/sharing.tsx | 190 +++++++++++++++++++++ 4 files changed, 309 insertions(+) create mode 100644 src/components/organisms/widgets/sharing.module.scss create mode 100644 src/components/organisms/widgets/sharing.stories.tsx create mode 100644 src/components/organisms/widgets/sharing.test.tsx create mode 100644 src/components/organisms/widgets/sharing.tsx (limited to 'src/components/organisms/widgets') diff --git a/src/components/organisms/widgets/sharing.module.scss b/src/components/organisms/widgets/sharing.module.scss new file mode 100644 index 0000000..e06d4e3 --- /dev/null +++ b/src/components/organisms/widgets/sharing.module.scss @@ -0,0 +1,10 @@ +@use "@styles/abstracts/mixins" as mix; + +.list { + display: flex; + flex-flow: row wrap; + gap: var(--spacing-xs); + margin: 0; + padding: 0 var(--spacing-2xs); + list-style-type: none; +} diff --git a/src/components/organisms/widgets/sharing.stories.tsx b/src/components/organisms/widgets/sharing.stories.tsx new file mode 100644 index 0000000..be20b84 --- /dev/null +++ b/src/components/organisms/widgets/sharing.stories.tsx @@ -0,0 +1,78 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { IntlProvider } from 'react-intl'; +import SharingWidget from './sharing'; + +export default { + title: 'Organisms/Widgets', + component: SharingWidget, + argTypes: { + data: { + description: 'The page data.', + type: { + name: 'object', + required: true, + value: {}, + }, + }, + expanded: { + control: { + type: null, + }, + description: 'Default widget state (expanded or collapsed).', + type: { + name: 'boolean', + required: true, + }, + }, + level: { + control: { + type: 'number', + }, + description: 'The heading level.', + type: { + name: 'number', + required: true, + }, + }, + media: { + control: { + type: null, + }, + description: 'An array of active and ordered sharing medium.', + type: { + name: 'string', + required: true, + }, + }, + title: { + control: { + type: 'text', + }, + description: 'The widget title.', + type: { + name: 'string', + required: true, + }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + + + +); + +export const Sharing = Template.bind({}); +Sharing.args = { + expanded: true, + data: { + excerpt: + 'Alias similique eius ducimus laudantium aspernatur. Est rem ut eum temporibus sit reprehenderit aut non molestias. Vel dolorem expedita labore quo inventore aliquid nihil nam. Possimus nobis enim quas corporis eos.', + title: 'Accusantium totam nostrum', + url: 'https://www.example.test', + }, + level: 2, + media: ['diaspora', 'facebook', 'linkedin', 'twitter', 'email'], + title: 'Sharing', +}; diff --git a/src/components/organisms/widgets/sharing.test.tsx b/src/components/organisms/widgets/sharing.test.tsx new file mode 100644 index 0000000..265dbe1 --- /dev/null +++ b/src/components/organisms/widgets/sharing.test.tsx @@ -0,0 +1,31 @@ +import { render, screen } from '@test-utils'; +import Sharing, { type SharingData } from './sharing'; + +const postData: SharingData = { + excerpt: 'A post excerpt', + title: 'A post title', + url: 'https://sharing-website.test', +}; + +describe('Sharing', () => { + it('renders a sharing widget', () => { + render( + + ); + expect( + screen.getByRole('link', { name: 'Share on facebook' }) + ).toBeInTheDocument(); + expect( + screen.getByRole('link', { name: 'Share on twitter' }) + ).toBeInTheDocument(); + expect( + screen.queryByRole('link', { name: 'Share on linkedin' }) + ).not.toBeInTheDocument(); + }); +}); diff --git a/src/components/organisms/widgets/sharing.tsx b/src/components/organisms/widgets/sharing.tsx new file mode 100644 index 0000000..ccd3a21 --- /dev/null +++ b/src/components/organisms/widgets/sharing.tsx @@ -0,0 +1,190 @@ +import SharingLink, { + type SharingMedium, +} from '@components/atoms/links/sharing-link'; +import Widget, { type WidgetProps } from '@components/molecules/layout/widget'; +import { VFC } from 'react'; +import { useIntl } from 'react-intl'; +import styles from './sharing.module.scss'; + +export type SharingData = { + /** + * The content excerpt. + */ + excerpt: string; + /** + * The content title. + */ + title: string; + /** + * The content url. + */ + url: string; +}; + +export type SharingProps = WidgetProps & { + /** + * The page data to share. + */ + data: SharingData; + /** + * A list of active and ordered sharing medium. + */ + media: SharingMedium[]; +}; + +/** + * Sharing widget component + * + * Render a list of sharing links inside a widget. + */ +const Sharing: VFC = ({ data, media, ...props }) => { + const intl = useIntl(); + + /** + * Build the Diaspora sharing url with provided data. + * + * @param {string} title - The content title. + * @param {string} url - The content url. + * @returns {string} The Diaspora url. + */ + const buildDiasporaUrl = (title: string, url: string): string => { + const titleParam = `title=${encodeURI(title)}`; + const urlParam = `url=${encodeURI(url)}`; + return `https://share.diasporafoundation.org/?${titleParam}&${urlParam}`; + }; + + /** + * Build the mailto url from provided data. + * + * @param {string} excerpt - The content excerpt. + * @param {string} title - The content title. + * @param {string} url - The content url. + * @returns {string} The mailto url with params. + */ + const buildEmailUrl = ( + excerpt: string, + title: string, + url: string + ): string => { + const intro = intl.formatMessage({ + defaultMessage: 'Introduction:', + description: 'Sharing: email content prefix', + id: 'yfgMcl', + }); + const readMore = intl.formatMessage({ + defaultMessage: 'Read more here:', + description: 'Sharing: content link prefix', + id: 'UsQske', + }); + const body = `${intro}\n\n"${excerpt}"\n\n${readMore} ${url}`; + const bodyParam = excerpt ? `body=${encodeURI(body)}` : ''; + + const subject = intl.formatMessage( + { + defaultMessage: 'You should read {title}', + description: 'Sharing: subject text', + id: 'azgQuH', + }, + { title } + ); + const subjectParam = `subject=${encodeURI(subject)}`; + + return `mailto:?${bodyParam}&${subjectParam}`; + }; + + /** + * Build the Facebook sharing url with provided data. + * + * @param {string} url - The content url. + * @returns {string} The Facebook url. + */ + const buildFacebookUrl = (url: string): string => { + const urlParam = `u=${encodeURI(url)}`; + return `https://www.facebook.com/sharer/sharer.php?${urlParam}`; + }; + + /** + * Build the Journal du Hacker sharing url with provided data. + * + * @param {string} title - The content title. + * @param {string} url - The content url. + * @returns {string} The Journal du Hacker url. + */ + const buildJdHUrl = (title: string, url: string): string => { + const titleParam = `title=${encodeURI(title)}`; + const urlParam = `url=${encodeURI(url)}`; + return `https://www.journalduhacker.net/stories/new?${titleParam}&${urlParam}`; + }; + + /** + * Build the LinkedIn sharing url with provided data. + * + * @param {string} url - The content url. + * @returns {string} The LinkedIn url. + */ + const buildLinkedInUrl = (url: string): string => { + const urlParam = `url=${encodeURI(url)}`; + return `https://www.linkedin.com/sharing/share-offsite/?${urlParam}`; + }; + + /** + * Build the Twitter sharing url with provided data. + * + * @param {string} title - The content title. + * @param {string} url - The content url. + * @returns {string} The Twitter url. + */ + const buildTwitterUrl = (title: string, url: string): string => { + const titleParam = `text=${encodeURI(title)}`; + const urlParam = `url=${encodeURI(url)}`; + return `https://twitter.com/intent/tweet?${titleParam}&${urlParam}`; + }; + + /** + * Retrieve the sharing url by medium id. + * + * @param {SharingMedium} medium - A sharing medium id. + * @returns {string} The sharing url. + */ + const getUrl = (medium: SharingMedium): string => { + const { excerpt, title, url } = data; + + switch (medium) { + case 'diaspora': + return buildDiasporaUrl(title, url); + case 'email': + return buildEmailUrl(excerpt, title, url); + case 'facebook': + return buildFacebookUrl(url); + case 'journal-du-hacker': + return buildJdHUrl(title, url); + case 'linkedin': + return buildLinkedInUrl(url); + case 'twitter': + return buildTwitterUrl(title, url); + default: + return '#'; + } + }; + + /** + * Get the sharing list items. + * + * @returns {JSX.Element[]} The sharing links wrapped with li element. + */ + const getItems = (): JSX.Element[] => { + return media.map((medium) => ( +
  • + +
  • + )); + }; + + return ( + +
      {getItems()}
    +
    + ); +}; + +export default Sharing; -- cgit v1.2.3 From b3ac82bba9605fa9d9c4b1d29c5a56a52e9de015 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Fri, 15 Apr 2022 17:38:16 +0200 Subject: chore: add a SocialMedia component --- .../organisms/widgets/social-media.module.scss | 10 ++++ .../organisms/widgets/social-media.stories.tsx | 56 ++++++++++++++++++++++ .../organisms/widgets/social-media.test.tsx | 33 +++++++++++++ src/components/organisms/widgets/social-media.tsx | 41 ++++++++++++++++ 4 files changed, 140 insertions(+) create mode 100644 src/components/organisms/widgets/social-media.module.scss create mode 100644 src/components/organisms/widgets/social-media.stories.tsx create mode 100644 src/components/organisms/widgets/social-media.test.tsx create mode 100644 src/components/organisms/widgets/social-media.tsx (limited to 'src/components/organisms/widgets') diff --git a/src/components/organisms/widgets/social-media.module.scss b/src/components/organisms/widgets/social-media.module.scss new file mode 100644 index 0000000..01b6c0e --- /dev/null +++ b/src/components/organisms/widgets/social-media.module.scss @@ -0,0 +1,10 @@ +@use "@styles/abstracts/placeholders"; + +.list { + @extend %reset-list; + + display: flex; + flex-flow: row wrap; + gap: var(--spacing-xs); + padding: 0 var(--spacing-2xs); +} diff --git a/src/components/organisms/widgets/social-media.stories.tsx b/src/components/organisms/widgets/social-media.stories.tsx new file mode 100644 index 0000000..2b84012 --- /dev/null +++ b/src/components/organisms/widgets/social-media.stories.tsx @@ -0,0 +1,56 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { IntlProvider } from 'react-intl'; +import SocialMediaWidget, { Media } from './social-media'; + +export default { + title: 'Organisms/Widgets', + component: SocialMediaWidget, + argTypes: { + level: { + control: { + type: 'number', + }, + description: 'The heading level.', + type: { + name: 'number', + required: true, + }, + }, + media: { + description: 'The links data.', + type: { + name: 'object', + required: true, + value: {}, + }, + }, + title: { + control: { + type: 'text', + }, + description: 'The widget title.', + type: { + name: 'string', + required: true, + }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + + + +); + +const media: Media[] = [ + { name: 'Github', url: '#' }, + { name: 'LinkedIn', url: '#' }, +]; + +export const SocialMedia = Template.bind({}); +SocialMedia.args = { + media, + title: 'Follow me', + level: 2, +}; diff --git a/src/components/organisms/widgets/social-media.test.tsx b/src/components/organisms/widgets/social-media.test.tsx new file mode 100644 index 0000000..e40db30 --- /dev/null +++ b/src/components/organisms/widgets/social-media.test.tsx @@ -0,0 +1,33 @@ +import { render, screen } from '@test-utils'; +import SocialMedia, { Media } from './social-media'; + +const media: Media[] = [ + { name: 'Github', url: '#' }, + { name: 'LinkedIn', url: '#' }, +]; +const title = 'Dolores ut ut'; +const titleLevel = 2; + +/** + * Next.js mock images with next/image component. So for now, I need to mock + * the svg files manually. + */ +jest.mock('@assets/images/social-media/github.svg', () => 'svg-file'); +jest.mock('@assets/images/social-media/linkedin.svg', () => 'svg-file'); + +describe('SocialMedia', () => { + it('renders the widget title', () => { + render(); + expect( + screen.getByRole('heading', { + level: titleLevel, + name: new RegExp(title, 'i'), + }) + ).toBeInTheDocument(); + }); + + it('renders the correct number of items', () => { + render(); + expect(screen.getAllByRole('listitem')).toHaveLength(media.length); + }); +}); diff --git a/src/components/organisms/widgets/social-media.tsx b/src/components/organisms/widgets/social-media.tsx new file mode 100644 index 0000000..58b2f73 --- /dev/null +++ b/src/components/organisms/widgets/social-media.tsx @@ -0,0 +1,41 @@ +import SocialLink, { + type SocialLinkProps, +} from '@components/atoms/links/social-link'; +import Widget, { type WidgetProps } from '@components/molecules/layout/widget'; +import { FC } from 'react'; +import styles from './social-media.module.scss'; + +export type Media = SocialLinkProps; + +export type SocialMediaProps = Pick & { + media: Media[]; +}; + +/** + * Social Media widget component + * + * Render a social media list with links. + */ +const SocialMedia: FC = ({ media, ...props }) => { + /** + * Retrieve the social media items. + * + * @param {SocialMedia[]} links - An array of social media name and url. + * @returns {JSX.Element[]} The social links. + */ + const getItems = (links: Media[]): JSX.Element[] => { + return links.map((link, index) => ( +
  • + +
  • + )); + }; + + return ( + +
      {getItems(media)}
    +
    + ); +}; + +export default SocialMedia; -- cgit v1.2.3 From d8a5c90ef58fa451c19e8d9e0aab0c493a0a6a9f Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Fri, 15 Apr 2022 22:56:36 +0200 Subject: chore: add an ImageWidget component --- .../organisms/widgets/image-widget.module.scss | 43 ++++++++ .../organisms/widgets/image-widget.stories.tsx | 113 +++++++++++++++++++++ .../organisms/widgets/image-widget.test.tsx | 54 ++++++++++ src/components/organisms/widgets/image-widget.tsx | 75 ++++++++++++++ 4 files changed, 285 insertions(+) create mode 100644 src/components/organisms/widgets/image-widget.module.scss create mode 100644 src/components/organisms/widgets/image-widget.stories.tsx create mode 100644 src/components/organisms/widgets/image-widget.test.tsx create mode 100644 src/components/organisms/widgets/image-widget.tsx (limited to 'src/components/organisms/widgets') diff --git a/src/components/organisms/widgets/image-widget.module.scss b/src/components/organisms/widgets/image-widget.module.scss new file mode 100644 index 0000000..78c0d26 --- /dev/null +++ b/src/components/organisms/widgets/image-widget.module.scss @@ -0,0 +1,43 @@ +@use "@styles/abstracts/functions" as fun; + +.img { + max-height: fun.convert-px(350); + margin: 0; +} + +.txt { + padding: var(--spacing-sm); +} + +.widget { + &--left { + .img { + margin-right: auto; + } + + .txt { + text-align: left; + } + } + + &--center { + .img { + margin-left: auto; + margin-right: auto; + } + + .txt { + text-align: center; + } + } + + &--right { + .img { + margin-left: auto; + } + + .txt { + text-align: right; + } + } +} diff --git a/src/components/organisms/widgets/image-widget.stories.tsx b/src/components/organisms/widgets/image-widget.stories.tsx new file mode 100644 index 0000000..1c2397b --- /dev/null +++ b/src/components/organisms/widgets/image-widget.stories.tsx @@ -0,0 +1,113 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { IntlProvider } from 'react-intl'; +import ImageWidgetComponent from './image-widget'; + +export default { + title: 'Organisms/Widgets', + component: ImageWidgetComponent, + args: { + alignment: 'left', + }, + argTypes: { + alignment: { + control: { + type: 'select', + }, + description: 'The content alignment.', + options: ['left', 'center', 'right'], + table: { + category: 'Options', + defaultValue: { summary: 'left' }, + }, + type: { + name: 'string', + required: false, + }, + }, + description: { + control: { + type: 'text', + }, + description: 'Add a caption image.', + table: { + category: 'Options', + }, + type: { + name: 'string', + required: false, + }, + }, + expanded: { + control: { + type: 'boolean', + }, + description: 'The state of the widget.', + type: { + name: 'boolean', + required: true, + }, + }, + img: { + description: 'An image object.', + type: { + name: 'object', + required: true, + value: {}, + }, + }, + level: { + control: { + type: 'number', + }, + description: 'The widget title level (hn).', + type: { + name: 'number', + required: true, + }, + }, + title: { + control: { + type: 'text', + }, + description: 'The widget title.', + type: { + name: 'string', + required: true, + }, + }, + url: { + control: { + type: 'text', + }, + description: 'Add a link to the image.', + table: { + category: 'Options', + }, + type: { + name: 'string', + required: false, + }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + + + +); + +const img = { + alt: 'Et perferendis quaerat', + height: 480, + src: 'http://placeimg.com/640/480/nature', + width: 640, +}; + +export const ImageWidget = Template.bind({}); +ImageWidget.args = { + expanded: true, + img, + level: 2, + title: 'Quo et totam', +}; diff --git a/src/components/organisms/widgets/image-widget.test.tsx b/src/components/organisms/widgets/image-widget.test.tsx new file mode 100644 index 0000000..8c24bd9 --- /dev/null +++ b/src/components/organisms/widgets/image-widget.test.tsx @@ -0,0 +1,54 @@ +import { render, screen } from '@test-utils'; +import ImageWidget from './image-widget'; + +const description = 'Ut vitae sit'; + +const img = { + alt: 'Et perferendis quaerat', + height: 480, + src: 'http://placeimg.com/640/480/nature', + width: 640, +}; + +const title = 'Fugiat cumque et'; +const titleLevel = 2; + +const url = '/another-page'; + +describe('ImageWidget', () => { + it('renders an image', () => { + render( + + ); + expect(screen.getByRole('img', { name: img.alt })).toBeInTheDocument(); + }); + + it('renders a link', () => { + render( + + ); + expect(screen.getByRole('link', { name: img.alt })).toHaveAttribute( + 'href', + url + ); + }); + + it('renders a description', () => { + render( + + ); + expect(screen.getByText(description)).toBeInTheDocument(); + }); +}); diff --git a/src/components/organisms/widgets/image-widget.tsx b/src/components/organisms/widgets/image-widget.tsx new file mode 100644 index 0000000..928d5ea --- /dev/null +++ b/src/components/organisms/widgets/image-widget.tsx @@ -0,0 +1,75 @@ +import ResponsiveImage from '@components/molecules/images/responsive-image'; +import Widget, { type WidgetProps } from '@components/molecules/layout/widget'; +import { VFC } from 'react'; +import styles from './image-widget.module.scss'; + +export type Alignment = 'left' | 'center' | 'right'; + +export type Image = { + /** + * An alternative text for the image. + */ + alt: string; + /** + * The image height. + */ + height: number; + /** + * The image source. + */ + src: string; + /** + * The image width. + */ + width: number; +}; + +export type ImageWidgetProps = Pick< + WidgetProps, + 'expanded' | 'level' | 'title' +> & { + /** + * The content alignment. + */ + alignment?: Alignment; + /** + * Add a caption to the image. + */ + description?: string; + /** + * An object describing the image. + */ + img: Image; + /** + * Add a link to the image. + */ + url?: string; +}; + +/** + * ImageWidget component + * + * Renders a widget that print an image and an optional text. + */ +const ImageWidget: VFC = ({ + alignment = 'left', + description, + img, + url, + ...props +}) => { + const alignmentClass = `widget--${alignment}`; + + return ( + + + + ); +}; + +export default ImageWidget; -- cgit v1.2.3 From 5a6e4eea16047083e2de0e91a1b3ed9be8d6eb68 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Sat, 16 Apr 2022 16:08:49 +0200 Subject: refactor: support React 18 I replaced the deprecated VFC type with FC type and made all children explicits. Formatjs is still not compatible with React 18 so I need to skip type checking when comitting. There are some type errors because of IntlProvider in Storybook stories. --- jest.setup.js | 1 + src/components/atoms/buttons/button-link.tsx | 6 +- src/components/atoms/buttons/button.tsx | 6 +- src/components/atoms/forms/checkbox.tsx | 4 +- src/components/atoms/forms/field.tsx | 4 +- src/components/atoms/forms/form.test.tsx | 6 +- src/components/atoms/forms/form.tsx | 6 +- src/components/atoms/forms/label.tsx | 6 +- src/components/atoms/forms/select.tsx | 4 +- src/components/atoms/headings/heading.tsx | 6 +- src/components/atoms/icons/arrow.tsx | 4 +- src/components/atoms/icons/career.tsx | 4 +- src/components/atoms/icons/cc-by-sa.tsx | 4 +- src/components/atoms/icons/close.tsx | 4 +- src/components/atoms/icons/cog.tsx | 4 +- src/components/atoms/icons/computer-screen.tsx | 4 +- src/components/atoms/icons/envelop.tsx | 4 +- src/components/atoms/icons/hamburger.tsx | 2 +- src/components/atoms/icons/home.tsx | 4 +- src/components/atoms/icons/magnifying-glass.tsx | 4 +- src/components/atoms/icons/moon.tsx | 6 +- src/components/atoms/icons/plus-minus.tsx | 2 +- src/components/atoms/icons/posts-stack.tsx | 4 +- src/components/atoms/icons/sun.tsx | 6 +- src/components/atoms/images/logo.tsx | 6 +- src/components/atoms/layout/copyright.tsx | 4 +- src/components/atoms/layout/main.tsx | 6 +- src/components/atoms/layout/no-script.tsx | 4 +- src/components/atoms/layout/notice.tsx | 4 +- src/components/atoms/layout/section.tsx | 4 +- src/components/atoms/links/link.module.scss | 20 ++++-- src/components/atoms/links/link.tsx | 6 +- src/components/atoms/links/nav-link.tsx | 4 +- src/components/atoms/links/sharing-link.tsx | 4 +- src/components/atoms/links/social-link.tsx | 4 +- src/components/atoms/lists/description-list.tsx | 4 +- src/components/atoms/lists/list.tsx | 4 +- src/components/atoms/loaders/progress-bar.tsx | 4 +- src/components/atoms/loaders/spinner.tsx | 4 +- src/components/molecules/buttons/back-to-top.tsx | 14 ++-- .../molecules/buttons/heading-button.tsx | 4 +- src/components/molecules/buttons/help-button.tsx | 6 +- .../molecules/forms/ackee-select.test.tsx | 16 +++-- src/components/molecules/forms/ackee-select.tsx | 6 +- src/components/molecules/forms/labelled-field.tsx | 4 +- src/components/molecules/forms/labelled-select.tsx | 15 +++-- src/components/molecules/forms/motion-toggle.tsx | 8 +-- .../molecules/forms/prism-theme-toggle.tsx | 8 +-- .../molecules/forms/select-with-tooltip.tsx | 10 +-- src/components/molecules/forms/theme-toggle.tsx | 8 +-- src/components/molecules/forms/toggle.tsx | 18 ++--- .../molecules/images/responsive-image.tsx | 10 +-- src/components/molecules/layout/branding.tsx | 12 ++-- src/components/molecules/layout/card.tsx | 8 +-- src/components/molecules/layout/flipping-logo.tsx | 10 +-- src/components/molecules/layout/meta.tsx | 4 +- src/components/molecules/layout/widget.tsx | 10 ++- src/components/molecules/modals/modal.test.tsx | 13 +++- src/components/molecules/modals/modal.tsx | 14 ++-- src/components/molecules/modals/tooltip.tsx | 4 +- src/components/molecules/nav/breadcrumb.tsx | 4 +- src/components/molecules/nav/nav.tsx | 4 +- .../organisms/forms/comment-form.stories.tsx | 1 - src/components/organisms/forms/comment-form.tsx | 4 +- src/components/organisms/forms/contact-form.tsx | 4 +- src/components/organisms/forms/search-form.tsx | 11 +++- src/components/organisms/layout/cards-list.tsx | 4 +- src/components/organisms/layout/footer.tsx | 13 ++-- src/components/organisms/layout/overview.tsx | 4 +- .../organisms/layout/summary.stories.tsx | 2 +- src/components/organisms/layout/summary.test.tsx | 2 +- src/components/organisms/layout/summary.tsx | 77 +++++++++++++--------- src/components/organisms/modals/search-modal.tsx | 8 +-- src/components/organisms/modals/settings-modal.tsx | 14 ++-- src/components/organisms/toolbar/main-nav.tsx | 18 +++-- src/components/organisms/toolbar/search.tsx | 16 ++--- src/components/organisms/toolbar/settings.tsx | 14 ++-- src/components/organisms/toolbar/toolbar.tsx | 4 +- src/components/organisms/widgets/image-widget.tsx | 34 ++++------ .../organisms/widgets/links-list-widget.tsx | 9 ++- src/components/organisms/widgets/sharing.tsx | 6 +- 81 files changed, 353 insertions(+), 289 deletions(-) (limited to 'src/components/organisms/widgets') diff --git a/jest.setup.js b/jest.setup.js index 5c5ecbc..d50c988 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -2,3 +2,4 @@ import '@testing-library/jest-dom/extend-expect'; import './__tests__/jest/__mocks__/matchMedia.mock'; jest.mock('next/dist/client/router', () => require('next-router-mock')); +jest.mock('next/dynamic', () => () => 'dynamic-import'); diff --git a/src/components/atoms/buttons/button-link.tsx b/src/components/atoms/buttons/button-link.tsx index 77a7f7b..906fa76 100644 --- a/src/components/atoms/buttons/button-link.tsx +++ b/src/components/atoms/buttons/button-link.tsx @@ -1,5 +1,5 @@ import Link from 'next/link'; -import { FC } from 'react'; +import { FC, ReactNode } from 'react'; import styles from './buttons.module.scss'; export type ButtonLinkProps = { @@ -7,6 +7,10 @@ export type ButtonLinkProps = { * ButtonLink accessible label. */ 'aria-label'?: string; + /** + * The button link body. + */ + children: ReactNode; /** * Set additional classnames to the button link. */ diff --git a/src/components/atoms/buttons/button.tsx b/src/components/atoms/buttons/button.tsx index 545c5c5..a6eef8b 100644 --- a/src/components/atoms/buttons/button.tsx +++ b/src/components/atoms/buttons/button.tsx @@ -1,7 +1,11 @@ -import { FC, MouseEventHandler } from 'react'; +import { FC, MouseEventHandler, ReactNode } from 'react'; import styles from './buttons.module.scss'; export type ButtonProps = { + /** + * The button body. + */ + children: ReactNode; /** * Set additional classnames to the button wrapper. */ diff --git a/src/components/atoms/forms/checkbox.tsx b/src/components/atoms/forms/checkbox.tsx index 8babcc8..aec97f0 100644 --- a/src/components/atoms/forms/checkbox.tsx +++ b/src/components/atoms/forms/checkbox.tsx @@ -1,4 +1,4 @@ -import { SetStateAction, VFC } from 'react'; +import { FC, SetStateAction } from 'react'; export type CheckboxProps = { /** @@ -32,7 +32,7 @@ export type CheckboxProps = { * * Render a checkbox type input. */ -const Checkbox: VFC = ({ value, setValue, ...props }) => { +const Checkbox: FC = ({ value, setValue, ...props }) => { return ( = ({ +const Field: FC = ({ className = '', setValue, type, diff --git a/src/components/atoms/forms/form.test.tsx b/src/components/atoms/forms/form.test.tsx index 9cd3c58..8b534f1 100644 --- a/src/components/atoms/forms/form.test.tsx +++ b/src/components/atoms/forms/form.test.tsx @@ -3,7 +3,11 @@ import Form from './form'; describe('Form', () => { it('renders a form', () => { - render(
    null}>
    ); + render( +
    null}> + Fields +
    + ); expect(screen.getByRole('form', { name: 'Jest form' })).toBeInTheDocument(); }); }); diff --git a/src/components/atoms/forms/form.tsx b/src/components/atoms/forms/form.tsx index 8e80930..ef8dce4 100644 --- a/src/components/atoms/forms/form.tsx +++ b/src/components/atoms/forms/form.tsx @@ -1,4 +1,4 @@ -import { Children, FC, FormEvent, Fragment } from 'react'; +import { Children, FC, FormEvent, Fragment, ReactNode } from 'react'; import styles from './forms.module.scss'; export type FormProps = { @@ -10,6 +10,10 @@ export type FormProps = { * One or more ids that refers to the form name. */ 'aria-labelledby'?: string; + /** + * The form body. + */ + children: ReactNode; /** * Set additional classnames to the form wrapper. */ diff --git a/src/components/atoms/forms/label.tsx b/src/components/atoms/forms/label.tsx index 8d57ee2..ce4c70f 100644 --- a/src/components/atoms/forms/label.tsx +++ b/src/components/atoms/forms/label.tsx @@ -1,7 +1,11 @@ -import { FC } from 'react'; +import { FC, ReactNode } from 'react'; import styles from './label.module.scss'; export type LabelProps = { + /** + * The label body. + */ + children: ReactNode; /** * Add classnames to the label. */ diff --git a/src/components/atoms/forms/select.tsx b/src/components/atoms/forms/select.tsx index 25e86e0..dbe9b37 100644 --- a/src/components/atoms/forms/select.tsx +++ b/src/components/atoms/forms/select.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent, SetStateAction, VFC } from 'react'; +import { ChangeEvent, FC, SetStateAction } from 'react'; import styles from './forms.module.scss'; export type SelectOptions = { @@ -60,7 +60,7 @@ export type SelectProps = { * * Render a HTML select element. */ -const Select: VFC = ({ +const Select: FC = ({ className = '', options, setValue, diff --git a/src/components/atoms/headings/heading.tsx b/src/components/atoms/headings/heading.tsx index 4703b5d..c5bf4ca 100644 --- a/src/components/atoms/headings/heading.tsx +++ b/src/components/atoms/headings/heading.tsx @@ -1,9 +1,13 @@ -import { FC } from 'react'; +import { FC, ReactNode } from 'react'; import styles from './heading.module.scss'; export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6; export type HeadingProps = { + /** + * The heading body. + */ + children: ReactNode; /** * Set additional classnames. */ diff --git a/src/components/atoms/icons/arrow.tsx b/src/components/atoms/icons/arrow.tsx index 5f3c460..2962530 100644 --- a/src/components/atoms/icons/arrow.tsx +++ b/src/components/atoms/icons/arrow.tsx @@ -1,4 +1,4 @@ -import { VFC } from 'react'; +import { FC } from 'react'; import styles from './arrow.module.scss'; export type ArrowDirection = 'top' | 'right' | 'bottom' | 'left'; @@ -19,7 +19,7 @@ export type ArrowProps = { * * Render a svg arrow icon. */ -const Arrow: VFC = ({ className = '', direction }) => { +const Arrow: FC = ({ className = '', direction }) => { const directionClass = styles[`icon--${direction}`]; const classes = `${styles.icon} ${directionClass} ${className}`; diff --git a/src/components/atoms/icons/career.tsx b/src/components/atoms/icons/career.tsx index 28edcc7..f28d399 100644 --- a/src/components/atoms/icons/career.tsx +++ b/src/components/atoms/icons/career.tsx @@ -1,4 +1,4 @@ -import { VFC } from 'react'; +import { FC } from 'react'; import styles from './career.module.scss'; export type CareerProps = { @@ -13,7 +13,7 @@ export type CareerProps = { * * Render a career svg icon. */ -const Career: VFC = ({ className = '' }) => { +const Career: FC = ({ className = '' }) => { return ( = ({ className = '' }) => { +const CCBySA: FC = ({ className = '' }) => { const intl = useIntl(); return ( diff --git a/src/components/atoms/icons/close.tsx b/src/components/atoms/icons/close.tsx index eb9ce7c..3e0adb5 100644 --- a/src/components/atoms/icons/close.tsx +++ b/src/components/atoms/icons/close.tsx @@ -1,4 +1,4 @@ -import { VFC } from 'react'; +import { FC } from 'react'; import styles from './close.module.scss'; export type CloseProps = { @@ -13,7 +13,7 @@ export type CloseProps = { * * Render a close svg icon. */ -const Close: VFC = ({ className = '' }) => { +const Close: FC = ({ className = '' }) => { return ( = ({ className = '' }) => { +const Cog: FC = ({ className = '' }) => { return ( = ({ className = '' }) => { +const ComputerScreen: FC = ({ className = '' }) => { return ( = ({ className = '' }) => { +const Envelop: FC = ({ className = '' }) => { return ( = ({ className = '' }) => { +const Home: FC = ({ className = '' }) => { return ( = ({ className = '' }) => { +const MagnifyingGlass: FC = ({ className = '' }) => { return ( = ({ className = '', title }) => { +const Moon: FC = ({ className = '', title }) => { return ( = ({ className = '' }) => { +const PostsStack: FC = ({ className = '' }) => { return ( = ({ className = '', title }) => { +const Sun: FC = ({ className = '', title }) => { return ( = ({ title }) => { +const Logo: FC = ({ title }) => { return ( = ({ owner, dates, icon }) => { +const Copyright: FC = ({ owner, dates, icon }) => { const getFormattedDate = (date: string) => { const datetime = new Date(date).toISOString(); diff --git a/src/components/atoms/layout/main.tsx b/src/components/atoms/layout/main.tsx index 4549328..d92a5c7 100644 --- a/src/components/atoms/layout/main.tsx +++ b/src/components/atoms/layout/main.tsx @@ -1,6 +1,10 @@ -import { FC } from 'react'; +import { FC, ReactNode } from 'react'; export type MainProps = { + /** + * The main body. + */ + children: ReactNode; /** * Set additional classnames to the main element. */ diff --git a/src/components/atoms/layout/no-script.tsx b/src/components/atoms/layout/no-script.tsx index 6358cf8..a503e0c 100644 --- a/src/components/atoms/layout/no-script.tsx +++ b/src/components/atoms/layout/no-script.tsx @@ -1,4 +1,4 @@ -import { VFC } from 'react'; +import { FC } from 'react'; import styles from './no-script.module.scss'; export type NoScriptProps = { @@ -12,7 +12,7 @@ export type NoScriptProps = { position?: 'initial' | 'top'; }; -const NoScript: VFC = ({ message, position = 'initial' }) => { +const NoScript: FC = ({ message, position = 'initial' }) => { const positionClass = styles[`noscript--${position}`]; return
    {message}
    ; diff --git a/src/components/atoms/layout/notice.tsx b/src/components/atoms/layout/notice.tsx index b6e09c5..115bd9c 100644 --- a/src/components/atoms/layout/notice.tsx +++ b/src/components/atoms/layout/notice.tsx @@ -1,4 +1,4 @@ -import { VFC } from 'react'; +import { FC } from 'react'; import styles from './notice.module.scss'; export type NoticeKind = 'error' | 'info' | 'success' | 'warning'; @@ -19,7 +19,7 @@ export type NoticeProps = { * * Render a colored message depending on notice kind. */ -const Notice: VFC = ({ kind, message }) => { +const Notice: FC = ({ kind, message }) => { const kindClass = `wrapper--${kind}`; return ( diff --git a/src/components/atoms/layout/section.tsx b/src/components/atoms/layout/section.tsx index f1bbb34..cb727ff 100644 --- a/src/components/atoms/layout/section.tsx +++ b/src/components/atoms/layout/section.tsx @@ -1,4 +1,4 @@ -import { ReactNode, VFC } from 'react'; +import { FC, ReactNode } from 'react'; import Heading from '../headings/heading'; import styles from './section.module.scss'; @@ -32,7 +32,7 @@ export type SectionProps = { * * Render a section element. */ -const Section: VFC = ({ +const Section: FC = ({ className = '', content, title, diff --git a/src/components/atoms/links/link.module.scss b/src/components/atoms/links/link.module.scss index e7ead86..d23667a 100644 --- a/src/components/atoms/links/link.module.scss +++ b/src/components/atoms/links/link.module.scss @@ -5,7 +5,9 @@ &[hreflang] { &::after { display: inline-block; - content: "\0000a0["attr(hreflang) "]"; + /* Prettier is removing spacing between content parts. */ + /* prettier-ignore */ + content: "\0000a0[" attr(hreflang) "]"; font-size: var(--font-size-sm); } } @@ -13,22 +15,30 @@ &--external { &::after { display: inline-block; - content: "\0000a0"url(fun.encode-svg('')); + /* Prettier is removing spacing between content parts. */ + /* prettier-ignore */ + content: "\0000a0" url(fun.encode-svg('')); } &:focus:not(:active)::after { - content: "\0000a0"url(fun.encode-svg('')); + /* Prettier is removing spacing between content parts. */ + /* prettier-ignore */ + content: "\0000a0" url(fun.encode-svg('')); } &[hreflang] { &::after { - content: "\0000a0["attr(hreflang) "]\0000a0"url(fun.encode-svg( + /* Prettier is removing spacing between content parts. */ + /* prettier-ignore */ + content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg( '' )); } &:focus:not(:active)::after { - content: "\0000a0["attr(hreflang) "]\0000a0"url(fun.encode-svg( + /* Prettier is removing spacing between content parts. */ + /* prettier-ignore */ + content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg( '' )); } diff --git a/src/components/atoms/links/link.tsx b/src/components/atoms/links/link.tsx index 87f11fc..674c07b 100644 --- a/src/components/atoms/links/link.tsx +++ b/src/components/atoms/links/link.tsx @@ -1,8 +1,12 @@ import NextLink from 'next/link'; -import { FC } from 'react'; +import { FC, ReactNode } from 'react'; import styles from './link.module.scss'; export type LinkProps = { + /** + * The link body. + */ + children: ReactNode; /** * Set additional classnames to the link. */ diff --git a/src/components/atoms/links/nav-link.tsx b/src/components/atoms/links/nav-link.tsx index 25c0e7d..7c6fede 100644 --- a/src/components/atoms/links/nav-link.tsx +++ b/src/components/atoms/links/nav-link.tsx @@ -1,5 +1,5 @@ import Link from 'next/link'; -import { VFC, ReactNode } from 'react'; +import { FC, ReactNode } from 'react'; import styles from './nav-link.module.scss'; export type NavLinkProps = { @@ -22,7 +22,7 @@ export type NavLinkProps = { * * Render a navigation link. */ -const NavLink: VFC = ({ href, label, logo }) => { +const NavLink: FC = ({ href, label, logo }) => { return ( diff --git a/src/components/atoms/links/sharing-link.tsx b/src/components/atoms/links/sharing-link.tsx index 3cd2dd1..ca53ef9 100644 --- a/src/components/atoms/links/sharing-link.tsx +++ b/src/components/atoms/links/sharing-link.tsx @@ -1,4 +1,4 @@ -import { VFC } from 'react'; +import { FC } from 'react'; import { useIntl } from 'react-intl'; import styles from './sharing-link.module.scss'; @@ -26,7 +26,7 @@ export type SharingLinkProps = { * * Render a sharing link. */ -const SharingLink: VFC = ({ medium, url }) => { +const SharingLink: FC = ({ medium, url }) => { const intl = useIntl(); const text = intl.formatMessage( { diff --git a/src/components/atoms/links/social-link.tsx b/src/components/atoms/links/social-link.tsx index 8c7c790..464bc60 100644 --- a/src/components/atoms/links/social-link.tsx +++ b/src/components/atoms/links/social-link.tsx @@ -2,7 +2,7 @@ import GithubIcon from '@assets/images/social-media/github.svg'; import GitlabIcon from '@assets/images/social-media/gitlab.svg'; import LinkedInIcon from '@assets/images/social-media/linkedin.svg'; import TwitterIcon from '@assets/images/social-media/twitter.svg'; -import { VFC } from 'react'; +import { FC } from 'react'; import styles from './social-link.module.scss'; export type SocialWebsite = 'Github' | 'Gitlab' | 'LinkedIn' | 'Twitter'; @@ -23,7 +23,7 @@ export type SocialLinkProps = { * * Render a social icon link. */ -const SocialLink: VFC = ({ name, url }) => { +const SocialLink: FC = ({ name, url }) => { /** * Retrieve a social link icon by id. * @param {string} id - The social website id. diff --git a/src/components/atoms/lists/description-list.tsx b/src/components/atoms/lists/description-list.tsx index 0a92465..a60a6a1 100644 --- a/src/components/atoms/lists/description-list.tsx +++ b/src/components/atoms/lists/description-list.tsx @@ -1,4 +1,4 @@ -import { VFC } from 'react'; +import { FC } from 'react'; import styles from './description-list.module.scss'; export type DescriptionListItem = { @@ -52,7 +52,7 @@ export type DescriptionListProps = { * * Render a description list. */ -const DescriptionList: VFC = ({ +const DescriptionList: FC = ({ className = '', descriptionClassName = '', groupClassName = '', diff --git a/src/components/atoms/lists/list.tsx b/src/components/atoms/lists/list.tsx index d100a31..6726802 100644 --- a/src/components/atoms/lists/list.tsx +++ b/src/components/atoms/lists/list.tsx @@ -1,4 +1,4 @@ -import { VFC } from 'react'; +import { FC } from 'react'; import styles from './list.module.scss'; export type ListItem = { @@ -44,7 +44,7 @@ export type ListProps = { * * Render either an ordered or an unordered list. */ -const List: VFC = ({ +const List: FC = ({ className = '', items, itemsClassName = '', diff --git a/src/components/atoms/loaders/progress-bar.tsx b/src/components/atoms/loaders/progress-bar.tsx index 1b1ff06..9bac847 100644 --- a/src/components/atoms/loaders/progress-bar.tsx +++ b/src/components/atoms/loaders/progress-bar.tsx @@ -1,4 +1,4 @@ -import { VFC } from 'react'; +import { FC } from 'react'; import styles from './progress-bar.module.scss'; export type ProgressBarProps = { @@ -29,7 +29,7 @@ export type ProgressBarProps = { * * Render a progress bar. */ -const ProgressBar: VFC = ({ +const ProgressBar: FC = ({ current, info, min, diff --git a/src/components/atoms/loaders/spinner.tsx b/src/components/atoms/loaders/spinner.tsx index bff0f25..6655141 100644 --- a/src/components/atoms/loaders/spinner.tsx +++ b/src/components/atoms/loaders/spinner.tsx @@ -1,4 +1,4 @@ -import { VFC } from 'react'; +import { FC } from 'react'; import { useIntl } from 'react-intl'; import styles from './spinner.module.scss'; @@ -14,7 +14,7 @@ export type SpinnerProps = { * * Render a loading message with animation. */ -const Spinner: VFC = ({ message }) => { +const Spinner: FC = ({ message }) => { const intl = useIntl(); return ( diff --git a/src/components/molecules/buttons/back-to-top.tsx b/src/components/molecules/buttons/back-to-top.tsx index 8a52231..bd1925a 100644 --- a/src/components/molecules/buttons/back-to-top.tsx +++ b/src/components/molecules/buttons/back-to-top.tsx @@ -1,18 +1,16 @@ -import ButtonLink from '@components/atoms/buttons/button-link'; +import ButtonLink, { + type ButtonLinkProps, +} from '@components/atoms/buttons/button-link'; import Arrow from '@components/atoms/icons/arrow'; -import { VFC } from 'react'; +import { FC } from 'react'; import { useIntl } from 'react-intl'; import styles from './back-to-top.module.scss'; -export type BackToTopProps = { +export type BackToTopProps = Pick & { /** * Set additional classnames to the button wrapper. */ className?: string; - /** - * An element id (without hashtag) to use as anchor. - */ - target: string; }; /** @@ -20,7 +18,7 @@ export type BackToTopProps = { * * Render a back to top link. */ -const BackToTop: VFC = ({ className = '', target }) => { +const BackToTop: FC = ({ className = '', target }) => { const intl = useIntl(); const linkName = intl.formatMessage({ defaultMessage: 'Back to top', diff --git a/src/components/molecules/buttons/heading-button.tsx b/src/components/molecules/buttons/heading-button.tsx index fc79749..0ed9a76 100644 --- a/src/components/molecules/buttons/heading-button.tsx +++ b/src/components/molecules/buttons/heading-button.tsx @@ -1,6 +1,6 @@ import Heading, { type HeadingProps } from '@components/atoms/headings/heading'; import PlusMinus from '@components/atoms/icons/plus-minus'; -import { SetStateAction, VFC } from 'react'; +import { FC, SetStateAction } from 'react'; import { useIntl } from 'react-intl'; import styles from './heading-button.module.scss'; @@ -28,7 +28,7 @@ export type HeadingButtonProps = Pick & { * * Render a button as accordion title to toggle body. */ -const HeadingButton: VFC = ({ +const HeadingButton: FC = ({ className = '', expanded, level, diff --git a/src/components/molecules/buttons/help-button.tsx b/src/components/molecules/buttons/help-button.tsx index aeb84ec..f19322f 100644 --- a/src/components/molecules/buttons/help-button.tsx +++ b/src/components/molecules/buttons/help-button.tsx @@ -1,5 +1,5 @@ -import Button, { ButtonProps } from '@components/atoms/buttons/button'; -import { VFC } from 'react'; +import Button, { type ButtonProps } from '@components/atoms/buttons/button'; +import { FC } from 'react'; import { useIntl } from 'react-intl'; import styles from './help-button.module.scss'; @@ -15,7 +15,7 @@ export type HelpButtonProps = Pick & { * * Render a button with an interrogation mark icon. */ -const HelpButton: VFC = ({ className = '', onClick }) => { +const HelpButton: FC = ({ className = '', onClick }) => { const intl = useIntl(); const text = intl.formatMessage({ defaultMessage: 'Help', diff --git a/src/components/molecules/forms/ackee-select.test.tsx b/src/components/molecules/forms/ackee-select.test.tsx index e1e6b2d..ec27922 100644 --- a/src/components/molecules/forms/ackee-select.test.tsx +++ b/src/components/molecules/forms/ackee-select.test.tsx @@ -1,5 +1,5 @@ -import userEvent from '@testing-library/user-event'; -import { render, screen } from '@test-utils'; +import user from '@testing-library/user-event'; +import { act, render, screen } from '@test-utils'; import AckeeSelect from './ackee-select'; describe('Select', () => { @@ -9,13 +9,15 @@ describe('Select', () => { expect(screen.queryByRole('combobox')).not.toHaveValue('partial'); }); - it('should correctly change value when user choose another option', () => { + it('should correctly change value when user choose another option', async () => { render(); - userEvent.selectOptions( - screen.getByRole('combobox'), - screen.getByRole('option', { name: 'Partial' }) - ); + await act(async () => { + await user.selectOptions( + screen.getByRole('combobox'), + screen.getByRole('option', { name: 'Partial' }) + ); + }); expect(screen.getByRole('combobox')).toHaveValue('partial'); expect(screen.queryByRole('combobox')).not.toHaveValue('full'); diff --git a/src/components/molecules/forms/ackee-select.tsx b/src/components/molecules/forms/ackee-select.tsx index 4a8410c..101e5b5 100644 --- a/src/components/molecules/forms/ackee-select.tsx +++ b/src/components/molecules/forms/ackee-select.tsx @@ -1,8 +1,8 @@ import { SelectOptions } from '@components/atoms/forms/select'; -import { Dispatch, SetStateAction, useState, VFC } from 'react'; +import { Dispatch, FC, SetStateAction, useState } from 'react'; import { useIntl } from 'react-intl'; import SelectWithTooltip, { - SelectWithTooltipProps, + type SelectWithTooltipProps, } from './select-with-tooltip'; export type AckeeOptions = 'full' | 'partial'; @@ -22,7 +22,7 @@ export type AckeeSelectProps = Pick< * * Render a select to set Ackee settings. */ -const AckeeSelect: VFC = ({ initialValue, ...props }) => { +const AckeeSelect: FC = ({ initialValue, ...props }) => { const intl = useIntl(); const [value, setValue] = useState(initialValue); diff --git a/src/components/molecules/forms/labelled-field.tsx b/src/components/molecules/forms/labelled-field.tsx index 08d0126..ecc9255 100644 --- a/src/components/molecules/forms/labelled-field.tsx +++ b/src/components/molecules/forms/labelled-field.tsx @@ -1,6 +1,6 @@ import Field, { type FieldProps } from '@components/atoms/forms/field'; import Label from '@components/atoms/forms/label'; -import { VFC } from 'react'; +import { FC } from 'react'; import styles from './labelled-field.module.scss'; export type LabelledFieldProps = FieldProps & { @@ -23,7 +23,7 @@ export type LabelledFieldProps = FieldProps & { * * Render a field tied to a label. */ -const LabelledField: VFC = ({ +const LabelledField: FC = ({ hideLabel = false, id, label, diff --git a/src/components/molecules/forms/labelled-select.tsx b/src/components/molecules/forms/labelled-select.tsx index 7d4237a..23057d0 100644 --- a/src/components/molecules/forms/labelled-select.tsx +++ b/src/components/molecules/forms/labelled-select.tsx @@ -1,6 +1,6 @@ -import Label, { LabelProps } from '@components/atoms/forms/label'; +import Label, { type LabelProps } from '@components/atoms/forms/label'; import Select, { type SelectProps } from '@components/atoms/forms/select'; -import { VFC } from 'react'; +import { FC } from 'react'; import styles from './labelled-select.module.scss'; export type LabelledSelectProps = Omit< @@ -14,7 +14,7 @@ export type LabelledSelectProps = Omit< /** * Set additional classnames to the label. */ - labelClassName?: string; + labelClassName?: LabelProps['className']; /** * The label position. Default: top. */ @@ -26,10 +26,15 @@ export type LabelledSelectProps = Omit< /** * Set additional classnames to the select field. */ - selectClassName?: string; + selectClassName?: SelectProps['className']; }; -const LabelledSelect: VFC = ({ +/** + * LabelledSelect component + * + * Render a select with a label. + */ +const LabelledSelect: FC = ({ id, label, labelClassName = '', diff --git a/src/components/molecules/forms/motion-toggle.tsx b/src/components/molecules/forms/motion-toggle.tsx index 9f30b42..24b54ae 100644 --- a/src/components/molecules/forms/motion-toggle.tsx +++ b/src/components/molecules/forms/motion-toggle.tsx @@ -1,8 +1,8 @@ import Toggle, { - ToggleChoices, - ToggleProps, + type ToggleChoices, + type ToggleProps, } from '@components/molecules/forms/toggle'; -import { useState, VFC } from 'react'; +import { FC, useState } from 'react'; import { useIntl } from 'react-intl'; export type MotionToggleProps = Pick; @@ -12,7 +12,7 @@ export type MotionToggleProps = Pick; * * Render a Toggle component to set reduce motion. */ -const MotionToggle: VFC = ({ value, ...props }) => { +const MotionToggle: FC = ({ value, ...props }) => { const intl = useIntl(); const [isDeactivated, setIsDeactivated] = useState(value); const reduceMotionLabel = intl.formatMessage({ diff --git a/src/components/molecules/forms/prism-theme-toggle.tsx b/src/components/molecules/forms/prism-theme-toggle.tsx index daee6bd..0b9c447 100644 --- a/src/components/molecules/forms/prism-theme-toggle.tsx +++ b/src/components/molecules/forms/prism-theme-toggle.tsx @@ -1,10 +1,10 @@ import Moon from '@components/atoms/icons/moon'; import Sun from '@components/atoms/icons/sun'; import Toggle, { - ToggleChoices, - ToggleProps, + type ToggleChoices, + type ToggleProps, } from '@components/molecules/forms/toggle'; -import { useState, VFC } from 'react'; +import { FC, useState } from 'react'; import { useIntl } from 'react-intl'; export type PrismThemeToggleProps = Pick< @@ -17,7 +17,7 @@ export type PrismThemeToggleProps = Pick< * * Render a Toggle component to set code blocks theme. */ -const PrismThemeToggle: VFC = ({ value, ...props }) => { +const PrismThemeToggle: FC = ({ value, ...props }) => { const intl = useIntl(); const [isDarkTheme, setIsDarkTheme] = useState(value); const themeLabel = intl.formatMessage({ diff --git a/src/components/molecules/forms/select-with-tooltip.tsx b/src/components/molecules/forms/select-with-tooltip.tsx index f537e1e..cf7b041 100644 --- a/src/components/molecules/forms/select-with-tooltip.tsx +++ b/src/components/molecules/forms/select-with-tooltip.tsx @@ -1,4 +1,4 @@ -import { useState, VFC } from 'react'; +import { FC, useState } from 'react'; import HelpButton from '../buttons/help-button'; import Tooltip, { type TooltipProps } from '../modals/tooltip'; import LabelledSelect, { type LabelledSelectProps } from './labelled-select'; @@ -9,14 +9,10 @@ export type SelectWithTooltipProps = Omit< 'labelPosition' > & Pick & { - /** - * The select label. - */ - label: string; /** * Set additional classnames to the tooltip wrapper. */ - tooltipClassName?: string; + tooltipClassName?: TooltipProps['className']; }; /** @@ -24,7 +20,7 @@ export type SelectWithTooltipProps = Omit< * * Render a select with a button to display a tooltip about options. */ -const SelectWithTooltip: VFC = ({ +const SelectWithTooltip: FC = ({ title, content, id, diff --git a/src/components/molecules/forms/theme-toggle.tsx b/src/components/molecules/forms/theme-toggle.tsx index eb56ce9..10c6c47 100644 --- a/src/components/molecules/forms/theme-toggle.tsx +++ b/src/components/molecules/forms/theme-toggle.tsx @@ -1,10 +1,10 @@ import Moon from '@components/atoms/icons/moon'; import Sun from '@components/atoms/icons/sun'; import Toggle, { - ToggleChoices, - ToggleProps, + type ToggleChoices, + type ToggleProps, } from '@components/molecules/forms/toggle'; -import { useState, VFC } from 'react'; +import { FC, useState } from 'react'; import { useIntl } from 'react-intl'; export type ThemeToggleProps = Pick; @@ -14,7 +14,7 @@ export type ThemeToggleProps = Pick; * * Render a Toggle component to set theme. */ -const ThemeToggle: VFC = ({ value, ...props }) => { +const ThemeToggle: FC = ({ value, ...props }) => { const intl = useIntl(); const [isDarkTheme, setIsDarkTheme] = useState(value); const themeLabel = intl.formatMessage({ diff --git a/src/components/molecules/forms/toggle.tsx b/src/components/molecules/forms/toggle.tsx index dff2d2d..288062d 100644 --- a/src/components/molecules/forms/toggle.tsx +++ b/src/components/molecules/forms/toggle.tsx @@ -1,6 +1,6 @@ -import Checkbox from '@components/atoms/forms/checkbox'; +import Checkbox, { type CheckboxProps } from '@components/atoms/forms/checkbox'; import Label, { type LabelProps } from '@components/atoms/forms/label'; -import { ReactNode, VFC } from 'react'; +import { FC, ReactNode } from 'react'; import styles from './toggle.module.scss'; export type ToggleChoices = { @@ -14,15 +14,11 @@ export type ToggleChoices = { right: ReactNode; }; -export type ToggleProps = { +export type ToggleProps = Pick & { /** * The toggle choices. */ choices: ToggleChoices; - /** - * The input id. - */ - id: string; /** * The toggle label. */ @@ -30,15 +26,11 @@ export type ToggleProps = { /** * Set additional classnames to the label. */ - labelClassName?: string; + labelClassName?: LabelProps['className']; /** * The label size. */ labelSize?: LabelProps['size']; - /** - * The input name. - */ - name: string; /** * The toggle value. True if checked. */ @@ -54,7 +46,7 @@ export type ToggleProps = { * * Render a toggle with a label and two choices. */ -const Toggle: VFC = ({ +const Toggle: FC = ({ choices, id, label, diff --git a/src/components/molecules/images/responsive-image.tsx b/src/components/molecules/images/responsive-image.tsx index 1d8787e..31cbcd1 100644 --- a/src/components/molecules/images/responsive-image.tsx +++ b/src/components/molecules/images/responsive-image.tsx @@ -1,6 +1,6 @@ -import Link from '@components/atoms/links/link'; -import Image, { ImageProps } from 'next/image'; -import { VFC } from 'react'; +import Link, { type LinkProps } from '@components/atoms/links/link'; +import Image, { type ImageProps } from 'next/image'; +import { FC } from 'react'; import styles from './responsive-image.module.scss'; export type ResponsiveImageProps = Omit< @@ -26,7 +26,7 @@ export type ResponsiveImageProps = Omit< /** * A link target. */ - target?: string; + target?: LinkProps['href']; /** * The image width. */ @@ -38,7 +38,7 @@ export type ResponsiveImageProps = Omit< * * Render a responsive image wrapped in a figure element. */ -const ResponsiveImage: VFC = ({ +const ResponsiveImage: FC = ({ alt, caption, className = '', diff --git a/src/components/molecules/layout/branding.tsx b/src/components/molecules/layout/branding.tsx index 9f564bf..9fe89e7 100644 --- a/src/components/molecules/layout/branding.tsx +++ b/src/components/molecules/layout/branding.tsx @@ -1,11 +1,11 @@ import Heading from '@components/atoms/headings/heading'; import Link from 'next/link'; -import { VFC } from 'react'; +import { FC } from 'react'; import { useIntl } from 'react-intl'; import styles from './branding.module.scss'; -import FlippingLogo from './flipping-logo'; +import FlippingLogo, { type FlippingLogoProps } from './flipping-logo'; -type BrandingProps = { +export type BrandingProps = Pick & { /** * The Branding baseline. */ @@ -14,10 +14,6 @@ type BrandingProps = { * Use H1 if the current page is homepage. Default: false. */ isHome?: boolean; - /** - * A photography URL. - */ - photo: string; /** * The Branding title; */ @@ -33,7 +29,7 @@ type BrandingProps = { * * Render the branding logo, title and optional baseline. */ -const Branding: VFC = ({ +const Branding: FC = ({ baseline, isHome = false, photo, diff --git a/src/components/molecules/layout/card.tsx b/src/components/molecules/layout/card.tsx index 23a0e54..89f100e 100644 --- a/src/components/molecules/layout/card.tsx +++ b/src/components/molecules/layout/card.tsx @@ -1,11 +1,11 @@ import ButtonLink from '@components/atoms/buttons/button-link'; import Heading, { type HeadingLevel } from '@components/atoms/headings/heading'; import DescriptionList, { - DescriptionListItem, + type DescriptionListItem, } from '@components/atoms/lists/description-list'; -import { VFC } from 'react'; +import { FC } from 'react'; import ResponsiveImage, { - ResponsiveImageProps, + type ResponsiveImageProps, } from '../images/responsive-image'; import styles from './card.module.scss'; @@ -68,7 +68,7 @@ export type CardProps = { * * Render a link with minimal information about its content. */ -const Card: VFC = ({ +const Card: FC = ({ className = '', cover, coverFit = 'cover', diff --git a/src/components/molecules/layout/flipping-logo.tsx b/src/components/molecules/layout/flipping-logo.tsx index 6f7645f..4a216ef 100644 --- a/src/components/molecules/layout/flipping-logo.tsx +++ b/src/components/molecules/layout/flipping-logo.tsx @@ -1,9 +1,9 @@ -import Logo from '@components/atoms/images/logo'; +import Logo, { type LogoProps } from '@components/atoms/images/logo'; import Image from 'next/image'; -import { VFC } from 'react'; +import { FC } from 'react'; import styles from './flipping-logo.module.scss'; -type FlippingLogoProps = { +export type FlippingLogoProps = { /** * Set additional classnames to the logo wrapper. */ @@ -15,7 +15,7 @@ type FlippingLogoProps = { /** * Logo image title. */ - logoTitle?: string; + logoTitle?: LogoProps['title']; /** * Photo url. */ @@ -27,7 +27,7 @@ type FlippingLogoProps = { * * Render a logo and a photo with a flipping effect. */ -const FlippingLogo: VFC = ({ +const FlippingLogo: FC = ({ className = '', altText, logoTitle, diff --git a/src/components/molecules/layout/meta.tsx b/src/components/molecules/layout/meta.tsx index 218ebd9..fcce473 100644 --- a/src/components/molecules/layout/meta.tsx +++ b/src/components/molecules/layout/meta.tsx @@ -2,7 +2,7 @@ import DescriptionList, { type DescriptionListProps, type DescriptionListItem, } from '@components/atoms/lists/description-list'; -import { ReactNode, VFC } from 'react'; +import { FC, ReactNode } from 'react'; export type MetaItem = { /** @@ -43,7 +43,7 @@ export type MetaProps = { * * Renders the page metadata. */ -const Meta: VFC = ({ data, ...props }) => { +const Meta: FC = ({ data, ...props }) => { /** * Transform the metadata to description list item format. * diff --git a/src/components/molecules/layout/widget.tsx b/src/components/molecules/layout/widget.tsx index c04362a..feb2add 100644 --- a/src/components/molecules/layout/widget.tsx +++ b/src/components/molecules/layout/widget.tsx @@ -1,11 +1,17 @@ -import { FC, useState } from 'react'; -import HeadingButton, { HeadingButtonProps } from '../buttons/heading-button'; +import { FC, ReactNode, useState } from 'react'; +import HeadingButton, { + type HeadingButtonProps, +} from '../buttons/heading-button'; import styles from './widget.module.scss'; export type WidgetProps = Pick< HeadingButtonProps, 'expanded' | 'level' | 'title' > & { + /** + * The widget body. + */ + children: ReactNode; /** * Set additional classnames to the widget wrapper. */ diff --git a/src/components/molecules/modals/modal.test.tsx b/src/components/molecules/modals/modal.test.tsx index 14fb224..9a0e237 100644 --- a/src/components/molecules/modals/modal.test.tsx +++ b/src/components/molecules/modals/modal.test.tsx @@ -1,9 +1,18 @@ import { render, screen } from '@test-utils'; import Modal from './modal'; +const title = 'A custom title'; +const children = + 'Labore ullam delectus sit modi quam dolores. Ratione id sint aliquid facilis ipsum. Unde necessitatibus provident minus.'; + describe('Modal', () => { it('renders a title', () => { - render(); - expect(screen.getByText('A custom title')).toBeInTheDocument(); + render({children}); + expect(screen.getByText(title)).toBeInTheDocument(); + }); + + it('renders the modal body', () => { + render({children}); + expect(screen.getByText(children)).toBeInTheDocument(); }); }); diff --git a/src/components/molecules/modals/modal.tsx b/src/components/molecules/modals/modal.tsx index 52ada57..58f5fa0 100644 --- a/src/components/molecules/modals/modal.tsx +++ b/src/components/molecules/modals/modal.tsx @@ -1,13 +1,17 @@ -import Heading from '@components/atoms/headings/heading'; -import { CogProps } from '@components/atoms/icons/cog'; -import { MagnifyingGlassProps } from '@components/atoms/icons/magnifying-glass'; +import Heading, { type HeadingProps } from '@components/atoms/headings/heading'; +import { type CogProps } from '@components/atoms/icons/cog'; +import { type MagnifyingGlassProps } from '@components/atoms/icons/magnifying-glass'; import dynamic from 'next/dynamic'; -import { FC } from 'react'; +import { FC, ReactNode } from 'react'; import styles from './modal.module.scss'; export type Icons = 'cogs' | 'search'; export type ModalProps = { + /** + * The modal body. + */ + children: ReactNode; /** * Set additional classnames. */ @@ -15,7 +19,7 @@ export type ModalProps = { /** * Set additional classnames to the heading. */ - headingClassName?: string; + headingClassName?: HeadingProps['className']; /** * A icon to illustrate the modal. */ diff --git a/src/components/molecules/modals/tooltip.tsx b/src/components/molecules/modals/tooltip.tsx index 73f36e7..80721f3 100644 --- a/src/components/molecules/modals/tooltip.tsx +++ b/src/components/molecules/modals/tooltip.tsx @@ -1,5 +1,5 @@ import List, { type ListItem } from '@components/atoms/lists/list'; -import { ReactNode, VFC } from 'react'; +import { FC, ReactNode } from 'react'; import styles from './tooltip.module.scss'; export type TooltipProps = { @@ -26,7 +26,7 @@ export type TooltipProps = { * * Render a tooltip modal. */ -const Tooltip: VFC = ({ +const Tooltip: FC = ({ className = '', content, icon, diff --git a/src/components/molecules/nav/breadcrumb.tsx b/src/components/molecules/nav/breadcrumb.tsx index 33af735..6dc86a0 100644 --- a/src/components/molecules/nav/breadcrumb.tsx +++ b/src/components/molecules/nav/breadcrumb.tsx @@ -1,7 +1,7 @@ import Link from '@components/atoms/links/link'; import { settings } from '@utils/config'; import Script from 'next/script'; -import { VFC } from 'react'; +import { FC } from 'react'; import { useIntl } from 'react-intl'; import { BreadcrumbList, ListItem, WithContext } from 'schema-dts'; import styles from './breadcrumb.module.scss'; @@ -37,7 +37,7 @@ export type BreadcrumbProps = { * * Render a breadcrumb navigation. */ -const Breadcrumb: VFC = ({ items, ...props }) => { +const Breadcrumb: FC = ({ items, ...props }) => { const intl = useIntl(); /** diff --git a/src/components/molecules/nav/nav.tsx b/src/components/molecules/nav/nav.tsx index 6ef9158..2666ea2 100644 --- a/src/components/molecules/nav/nav.tsx +++ b/src/components/molecules/nav/nav.tsx @@ -1,6 +1,6 @@ import Link from '@components/atoms/links/link'; import NavLink from '@components/atoms/links/nav-link'; -import { ReactNode, VFC } from 'react'; +import { FC, ReactNode } from 'react'; import styles from './nav.module.scss'; export type NavItem = { @@ -46,7 +46,7 @@ export type NavProps = { * * Render the nav links. */ -const Nav: VFC = ({ +const Nav: FC = ({ className = '', items, kind, diff --git a/src/components/organisms/forms/comment-form.stories.tsx b/src/components/organisms/forms/comment-form.stories.tsx index 1ab7cf2..670176c 100644 --- a/src/components/organisms/forms/comment-form.stories.tsx +++ b/src/components/organisms/forms/comment-form.stories.tsx @@ -1,4 +1,3 @@ -import Notice from '@components/atoms/layout/notice'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import { IntlProvider } from 'react-intl'; import CommentFormComponent from './comment-form'; diff --git a/src/components/organisms/forms/comment-form.tsx b/src/components/organisms/forms/comment-form.tsx index 6acbf94..d7cb0f5 100644 --- a/src/components/organisms/forms/comment-form.tsx +++ b/src/components/organisms/forms/comment-form.tsx @@ -3,7 +3,7 @@ import Form from '@components/atoms/forms/form'; import Heading, { type HeadingLevel } from '@components/atoms/headings/heading'; import Spinner from '@components/atoms/loaders/spinner'; import LabelledField from '@components/molecules/forms/labelled-field'; -import { ReactNode, useState, VFC } from 'react'; +import { FC, ReactNode, useState } from 'react'; import { useIntl } from 'react-intl'; import styles from './comment-form.module.scss'; @@ -31,7 +31,7 @@ export type CommentFormProps = { titleLevel?: HeadingLevel; }; -const CommentForm: VFC = ({ +const CommentForm: FC = ({ className = '', Notice, saveComment, diff --git a/src/components/organisms/forms/contact-form.tsx b/src/components/organisms/forms/contact-form.tsx index 994244a..4a6902b 100644 --- a/src/components/organisms/forms/contact-form.tsx +++ b/src/components/organisms/forms/contact-form.tsx @@ -2,7 +2,7 @@ import Button from '@components/atoms/buttons/button'; import Form from '@components/atoms/forms/form'; import Spinner from '@components/atoms/loaders/spinner'; import LabelledField from '@components/molecules/forms/labelled-field'; -import { ReactNode, useState, VFC } from 'react'; +import { FC, ReactNode, useState } from 'react'; import { useIntl } from 'react-intl'; import styles from './contact-form.module.scss'; @@ -27,7 +27,7 @@ export type ContactFormProps = { * * Render a contact form. */ -const ContactForm: VFC = ({ +const ContactForm: FC = ({ className = '', Notice, sendMail, diff --git a/src/components/organisms/forms/search-form.tsx b/src/components/organisms/forms/search-form.tsx index 351d93c..18b7c08 100644 --- a/src/components/organisms/forms/search-form.tsx +++ b/src/components/organisms/forms/search-form.tsx @@ -2,15 +2,20 @@ import Button from '@components/atoms/buttons/button'; import Form from '@components/atoms/forms/form'; import MagnifyingGlass from '@components/atoms/icons/magnifying-glass'; import LabelledField, { - LabelledFieldProps, + type LabelledFieldProps, } from '@components/molecules/forms/labelled-field'; -import { useState, VFC } from 'react'; +import { FC, useState } from 'react'; import { useIntl } from 'react-intl'; import styles from './search-form.module.scss'; export type SearchFormProps = Pick; -const SearchForm: VFC = ({ hideLabel }) => { +/** + * SearchForm component + * + * Render a search form. + */ +const SearchForm: FC = ({ hideLabel }) => { const intl = useIntl(); const fieldLabel = intl.formatMessage({ defaultMessage: 'Search for:', diff --git a/src/components/organisms/layout/cards-list.tsx b/src/components/organisms/layout/cards-list.tsx index a53df0d..33ffe23 100644 --- a/src/components/organisms/layout/cards-list.tsx +++ b/src/components/organisms/layout/cards-list.tsx @@ -3,7 +3,7 @@ import List, { type ListProps, } from '@components/atoms/lists/list'; import Card, { type CardProps } from '@components/molecules/layout/card'; -import { VFC } from 'react'; +import { FC } from 'react'; import styles from './cards-list.module.scss'; export type CardsListItem = Omit< @@ -37,7 +37,7 @@ export type CardsListProps = { * * Return a list of Card components. */ -const CardsList: VFC = ({ +const CardsList: FC = ({ coverFit, items, kind = 'unordered', diff --git a/src/components/organisms/layout/footer.tsx b/src/components/organisms/layout/footer.tsx index c9cb067..15bfa24 100644 --- a/src/components/organisms/layout/footer.tsx +++ b/src/components/organisms/layout/footer.tsx @@ -1,7 +1,9 @@ -import Copyright, { CopyrightProps } from '@components/atoms/layout/copyright'; +import Copyright, { + type CopyrightProps, +} from '@components/atoms/layout/copyright'; import BackToTop from '@components/molecules/buttons/back-to-top'; import Nav, { type NavItem } from '@components/molecules/nav/nav'; -import { VFC } from 'react'; +import { FC } from 'react'; import styles from './footer.module.scss'; export type FooterProps = { @@ -28,12 +30,7 @@ export type FooterProps = { * * Renders a footer with copyright and nav; */ -const Footer: VFC = ({ - className, - copyright, - navItems, - topId, -}) => { +const Footer: FC = ({ className, copyright, navItems, topId }) => { return (