From 7d9f874364ec6e255e62eb3027011bfed267904c Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Tue, 17 May 2022 18:37:38 +0200 Subject: chore: adjust articles styles * change animation on article card hover * change comments section alignment --- src/components/atoms/buttons/buttons.module.scss | 2 +- src/components/atoms/headings/heading.module.scss | 16 +++- src/components/atoms/headings/heading.stories.tsx | 26 +++++++ src/components/atoms/headings/heading.tsx | 58 ++++++++++++--- .../molecules/buttons/heading-button.module.scss | 15 ++-- .../molecules/buttons/heading-button.stories.tsx | 73 ++++++------------- src/components/organisms/forms/comment-form.tsx | 14 +++- .../organisms/layout/summary.module.scss | 7 +- .../templates/page/page-layout.module.scss | 9 ++- src/components/templates/page/page-layout.tsx | 85 ++++++++++++++-------- src/pages/article/[slug].tsx | 18 ++++- src/styles/pages/article.module.scss | 1 + 12 files changed, 211 insertions(+), 113 deletions(-) diff --git a/src/components/atoms/buttons/buttons.module.scss b/src/components/atoms/buttons/buttons.module.scss index 36c66b6..2444bb1 100644 --- a/src/components/atoms/buttons/buttons.module.scss +++ b/src/components/atoms/buttons/buttons.module.scss @@ -16,7 +16,7 @@ } &--rectangle { - padding: var(--spacing-2xs) var(--spacing-md); + padding: var(--spacing-2xs) var(--spacing-sm); } &--square, diff --git a/src/components/atoms/headings/heading.module.scss b/src/components/atoms/headings/heading.module.scss index 8620f6f..a420bc1 100644 --- a/src/components/atoms/headings/heading.module.scss +++ b/src/components/atoms/headings/heading.module.scss @@ -6,11 +6,23 @@ letter-spacing: 0.01ex; &--regular { - margin: 0; + margin-bottom: 0; + margin-top: 0; + } + + &--left { + text-align: left; + } + + &--center { + width: fit-content; + margin-left: auto; + margin-right: auto; } &--margin { - margin: 0 0 var(--spacing-sm); + margin-top: 0; + margin-bottom: var(--spacing-sm); & + & { margin-top: var(--spacing-md); diff --git a/src/components/atoms/headings/heading.stories.tsx b/src/components/atoms/headings/heading.stories.tsx index da5a718..0e3885d 100644 --- a/src/components/atoms/headings/heading.stories.tsx +++ b/src/components/atoms/headings/heading.stories.tsx @@ -8,10 +8,26 @@ export default { title: 'Atoms/Typography/Headings', component: Heading, args: { + alignment: 'left', isFake: false, withMargin: true, }, argTypes: { + alignment: { + control: { + type: 'select', + }, + description: 'The title alignment.', + options: ['center', 'left'], + table: { + category: 'Options', + defaultValue: { summary: 'left' }, + }, + type: { + name: 'string', + required: false, + }, + }, className: { control: { type: 'text', @@ -32,6 +48,16 @@ export default { required: true, }, }, + id: { + control: { + type: 'text', + }, + description: 'An unique id.', + type: { + name: 'string', + required: false, + }, + }, isFake: { control: { type: 'boolean', diff --git a/src/components/atoms/headings/heading.tsx b/src/components/atoms/headings/heading.tsx index c5bf4ca..e385249 100644 --- a/src/components/atoms/headings/heading.tsx +++ b/src/components/atoms/headings/heading.tsx @@ -1,9 +1,19 @@ -import { FC, ReactNode } from 'react'; +import { + createElement, + ForwardedRef, + forwardRef, + ForwardRefRenderFunction, + ReactNode, +} from 'react'; import styles from './heading.module.scss'; export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6; export type HeadingProps = { + /** + * The title alignment. Default: left; + */ + alignment?: 'center' | 'left'; /** * The heading body. */ @@ -30,31 +40,55 @@ export type HeadingProps = { withMargin?: boolean; }; +type TitleTagProps = Pick & { + tagName: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p'; +}; + +const TitleTag = forwardRef< + HTMLHeadingElement | HTMLParagraphElement, + TitleTagProps +>( + ( + { children, tagName, ...props }, + ref: ForwardedRef + ) => { + return createElement(tagName, { ...props, ref }, children); + } +); +TitleTag.displayName = 'TitleTag'; + /** * Heading component. * * Render an HTML heading element or a paragraph with heading styles. */ -const Heading: FC = ({ - children, - className, - id, - isFake = false, - level, - withMargin = true, -}) => { - const TitleTag = isFake ? `p` : (`h${level}` as keyof JSX.IntrinsicElements); +const Heading: ForwardRefRenderFunction = ( + { + alignment = 'left', + children, + className, + id, + isFake = false, + level, + withMargin = true, + }, + ref: ForwardedRef +) => { + const tagName = isFake ? 'p' : (`h${level}` as TitleTagProps['tagName']); const levelClass = `heading--${level}`; + const alignmentClass = `heading--${alignment}`; const marginClass = withMargin ? 'heading--margin' : 'heading--regular'; return ( {children} ); }; -export default Heading; +export default forwardRef(Heading); diff --git a/src/components/molecules/buttons/heading-button.module.scss b/src/components/molecules/buttons/heading-button.module.scss index 9c278e4..3c69221 100644 --- a/src/components/molecules/buttons/heading-button.module.scss +++ b/src/components/molecules/buttons/heading-button.module.scss @@ -20,6 +20,14 @@ border-bottom: fun.convert-px(2) solid var(--color-primary-dark); cursor: pointer; + .heading { + padding: var(--spacing-2xs) 0; + background: none; + font-size: var(--font-size-xl); + font-weight: 500; + text-align: left; + } + &:hover, &:focus { .icon { @@ -34,10 +42,3 @@ } } } - -.heading { - padding: var(--spacing-2xs) 0; - background: none; - font-size: var(--font-size-xl); - text-align: left; -} diff --git a/src/components/molecules/buttons/heading-button.stories.tsx b/src/components/molecules/buttons/heading-button.stories.tsx index b0e1465..d1b5ed4 100644 --- a/src/components/molecules/buttons/heading-button.stories.tsx +++ b/src/components/molecules/buttons/heading-button.stories.tsx @@ -1,6 +1,5 @@ import { ComponentMeta, ComponentStory } from '@storybook/react'; import { useState } from 'react'; -import { IntlProvider } from 'react-intl'; import HeadingButtonComponent from './heading-button'; /** @@ -10,6 +9,19 @@ export default { title: 'Molecules/Buttons/HeadingButton', component: HeadingButtonComponent, argTypes: { + className: { + control: { + type: 'text', + }, + description: 'Set additional classnames to the button.', + table: { + category: 'Styles', + }, + type: { + name: 'string', + required: false, + }, + }, expanded: { control: { type: null, @@ -53,13 +65,6 @@ export default { }, }, }, - decorators: [ - (Story) => ( - - - - ), - ], } as ComponentMeta; const Template: ComponentStory = ({ @@ -79,55 +84,21 @@ const Template: ComponentStory = ({ }; /** - * Heading Button Stories - Level 1 + * Heading Button Stories - Expanded */ -export const Level1 = Template.bind({}); -Level1.args = { - level: 1, - title: 'Your title', -}; - -/** - * Heading Button Stories - Level 2 - */ -export const Level2 = Template.bind({}); -Level2.args = { +export const Expanded = Template.bind({}); +Expanded.args = { + expanded: true, level: 2, title: 'Your title', }; /** - * Heading Button Stories - Level 3 + * Heading Button Stories - Collapsed */ -export const Level3 = Template.bind({}); -Level3.args = { - level: 3, - title: 'Your title', -}; - -/** - * Heading Button Stories - Level 4 - */ -export const Level4 = Template.bind({}); -Level4.args = { - level: 4, - title: 'Your title', -}; - -/** - * Heading Button Stories - Level 5 - */ -export const Level5 = Template.bind({}); -Level5.args = { - level: 5, - title: 'Your title', -}; - -/** - * Heading Button Stories - Level 6 - */ -export const Level6 = Template.bind({}); -Level6.args = { - level: 6, +export const Collapsed = Template.bind({}); +Collapsed.args = { + expanded: false, + level: 2, title: 'Your title', }; diff --git a/src/components/organisms/forms/comment-form.tsx b/src/components/organisms/forms/comment-form.tsx index 5ff4ea4..b2c725f 100644 --- a/src/components/organisms/forms/comment-form.tsx +++ b/src/components/organisms/forms/comment-form.tsx @@ -1,6 +1,9 @@ import Button from '@components/atoms/buttons/button'; import Form, { type FormProps } from '@components/atoms/forms/form'; -import Heading, { type HeadingLevel } from '@components/atoms/headings/heading'; +import Heading, { + type HeadingProps, + type HeadingLevel, +} from '@components/atoms/headings/heading'; import Spinner from '@components/atoms/loaders/spinner'; import LabelledField from '@components/molecules/forms/labelled-field'; import { FC, ReactNode, useState } from 'react'; @@ -34,7 +37,11 @@ export type CommentFormProps = Pick & { */ title?: string; /** - * The title level. + * The form title alignment. Default: left. + */ + titleAlignment?: HeadingProps['alignment']; + /** + * The title level. Default: 2. */ titleLevel?: HeadingLevel; }; @@ -44,6 +51,7 @@ const CommentForm: FC = ({ parentId, saveComment, title, + titleAlignment, titleLevel = 2, ...props }) => { @@ -117,7 +125,7 @@ const CommentForm: FC = ({ {...props} > {title && ( - + {title} )} diff --git a/src/components/organisms/layout/summary.module.scss b/src/components/organisms/layout/summary.module.scss index 7e86dd2..62dfc0e 100644 --- a/src/components/organisms/layout/summary.module.scss +++ b/src/components/organisms/layout/summary.module.scss @@ -29,8 +29,11 @@ &:hover { .icon { - transform: scaleX(1.4); - transform-origin: left; + --icon-size: #{fun.convert-px(35)}; + + :global { + animation: pulse 1.5s ease-in-out 0.2s infinite; + } } } } diff --git a/src/components/templates/page/page-layout.module.scss b/src/components/templates/page/page-layout.module.scss index 83e5a80..c6b4e8d 100644 --- a/src/components/templates/page/page-layout.module.scss +++ b/src/components/templates/page/page-layout.module.scss @@ -86,7 +86,14 @@ &__section { grid-column: 2; - margin: var(--spacing-md) 0 0; + + &:first-child { + margin: var(--spacing-md) 0 0; + } + } + + &__no-comments { + text-align: center; } &__form { diff --git a/src/components/templates/page/page-layout.tsx b/src/components/templates/page/page-layout.tsx index f3f3ea8..2ff2084 100644 --- a/src/components/templates/page/page-layout.tsx +++ b/src/components/templates/page/page-layout.tsx @@ -1,6 +1,7 @@ import Heading from '@components/atoms/headings/heading'; import Notice, { type NoticeKind } from '@components/atoms/layout/notice'; import Sidebar from '@components/atoms/layout/sidebar'; +import { MetaData } from '@components/molecules/layout/meta'; import PageFooter, { type PageFooterProps, } from '@components/molecules/layout/page-footer'; @@ -118,10 +119,10 @@ const PageLayout: FC = ({ const isMounted = useIsMounted(bodyRef); const hasComments = Array.isArray(comments) && comments.length > 0; - const hasCommentsSection = hasComments || allowComments; - const articleModifier = hasCommentsSection - ? 'article--has-comments' - : 'article--no-comments'; + const articleModifier = + hasComments || allowComments + ? 'article--has-comments' + : 'article--no-comments'; const [status, setStatus] = useState('info'); const [statusMessage, setStatusMessage] = useState(''); @@ -175,6 +176,15 @@ const PageLayout: FC = ({ } }; + /** + * Check if meta properties are defined. + * + * @param {MetaData} meta - The metadata. + */ + const hasMeta = (meta: MetaData) => { + return Object.values(meta).every((value) => value === null); + }; + return ( = ({ {children} )} - + {footerMeta && hasMeta(footerMeta) && ( + + )} = ({ > {widgets} - {hasCommentsSection && ( -
- {hasComments && ( -
- {commentsTitle} + {allowComments && ( +
+
+ + {commentsTitle} + + {hasComments ? ( - ) : undefined + ) } saveComment={saveComment} /> -
- )} - {allowComments && ( -
- - ) : undefined - } - /> -
- )} + ) : ( +

+ {intl.formatMessage({ + defaultMessage: 'No comments.', + id: 'sBwfCy', + description: 'PageLayout: no comments text', + })} +

+ )} +
+
+ + ) + } + /> +
)}
diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx index 995e3a9..a0fb7fc 100644 --- a/src/pages/article/[slug].tsx +++ b/src/pages/article/[slug].tsx @@ -15,7 +15,10 @@ import { type Article, type Comment } from '@ts/types/app'; import { loadTranslation, type Messages } from '@utils/helpers/i18n'; import useAddPrismClassAttr from '@utils/hooks/use-add-prism-class-attr'; import useBreadcrumb from '@utils/hooks/use-breadcrumb'; -import usePrismPlugins, { PrismPlugin } from '@utils/hooks/use-prism-plugins'; +import usePrismPlugins, { + type PrismPlugin, +} from '@utils/hooks/use-prism-plugins'; +import useReadingTime from '@utils/hooks/use-reading-time'; import useSettings from '@utils/hooks/use-settings'; import { GetStaticPaths, GetStaticProps, NextPage } from 'next'; import Head from 'next/head'; @@ -38,7 +41,16 @@ type ArticlePageProps = { */ const ArticlePage: NextPage = ({ comments, post }) => { const { content, id, intro, meta, slug, title } = post; - const { author, commentsCount, cover, dates, seo, thematics, topics } = meta; + const { + author, + commentsCount, + cover, + dates, + seo, + thematics, + topics, + wordsCount, + } = meta; const { data } = useSWR(() => id, getPostComments, { fallbackData: comments, }); @@ -46,11 +58,13 @@ const ArticlePage: NextPage = ({ comments, post }) => { title, url: `/article/${slug}`, }); + const readingTime = useReadingTime(wordsCount || 0, true); const headerMeta: PageLayoutProps['headerMeta'] = { author: author?.name, publication: { date: dates.publication }, update: dates.update ? { date: dates.update } : undefined, + readingTime, thematics: thematics && thematics.map((thematic) => ( diff --git a/src/styles/pages/article.module.scss b/src/styles/pages/article.module.scss index a42c633..04acad9 100644 --- a/src/styles/pages/article.module.scss +++ b/src/styles/pages/article.module.scss @@ -31,6 +31,7 @@ @include mix.media("screen") { @include mix.dimensions("md") { width: min-content; + gap: var(--spacing-2xs); } } } -- cgit v1.2.3