diff options
| author | Armand Philippot <git@armandphilippot.com> | 2022-05-17 18:37:38 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2022-05-17 18:37:38 +0200 |
| commit | 7d9f874364ec6e255e62eb3027011bfed267904c (patch) | |
| tree | 711e3ce9fba99abdff89016f15b9bc763d61f03f /src | |
| parent | 9a186b357b32325cf77fdce39a6c6c9aff76d8a5 (diff) | |
chore: adjust articles styles
* change animation on article card hover
* change comments section alignment
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/atoms/buttons/buttons.module.scss | 2 | ||||
| -rw-r--r-- | src/components/atoms/headings/heading.module.scss | 16 | ||||
| -rw-r--r-- | src/components/atoms/headings/heading.stories.tsx | 26 | ||||
| -rw-r--r-- | src/components/atoms/headings/heading.tsx | 58 | ||||
| -rw-r--r-- | src/components/molecules/buttons/heading-button.module.scss | 15 | ||||
| -rw-r--r-- | src/components/molecules/buttons/heading-button.stories.tsx | 73 | ||||
| -rw-r--r-- | src/components/organisms/forms/comment-form.tsx | 14 | ||||
| -rw-r--r-- | src/components/organisms/layout/summary.module.scss | 7 | ||||
| -rw-r--r-- | src/components/templates/page/page-layout.module.scss | 9 | ||||
| -rw-r--r-- | src/components/templates/page/page-layout.tsx | 85 | ||||
| -rw-r--r-- | src/pages/article/[slug].tsx | 18 | ||||
| -rw-r--r-- | 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,10 +1,20 @@ -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. */ children: ReactNode; @@ -30,31 +40,55 @@ export type HeadingProps = { withMargin?: boolean; }; +type TitleTagProps = Pick<HeadingProps, 'children' | 'className' | 'id'> & { + tagName: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p'; +}; + +const TitleTag = forwardRef< + HTMLHeadingElement | HTMLParagraphElement, + TitleTagProps +>( + ( + { children, tagName, ...props }, + ref: ForwardedRef<HTMLHeadingElement | HTMLParagraphElement> + ) => { + 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<HeadingProps> = ({ - children, - className, - id, - isFake = false, - level, - withMargin = true, -}) => { - const TitleTag = isFake ? `p` : (`h${level}` as keyof JSX.IntrinsicElements); +const Heading: ForwardRefRenderFunction<HTMLDivElement, HeadingProps> = ( + { + alignment = 'left', + children, + className, + id, + isFake = false, + level, + withMargin = true, + }, + ref: ForwardedRef<HTMLHeadingElement | HTMLParagraphElement> +) => { + 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 ( <TitleTag - className={`${styles.heading} ${styles[levelClass]} ${styles[marginClass]} ${className}`} + tagName={tagName} + className={`${styles.heading} ${styles[levelClass]} ${styles[alignmentClass]} ${styles[marginClass]} ${className}`} id={id} + ref={ref} > {children} </TitleTag> ); }; -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) => ( - <IntlProvider locale="en"> - <Story /> - </IntlProvider> - ), - ], } as ComponentMeta<typeof HeadingButtonComponent>; const Template: ComponentStory<typeof HeadingButtonComponent> = ({ @@ -79,55 +84,21 @@ const Template: ComponentStory<typeof HeadingButtonComponent> = ({ }; /** - * 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<FormProps, 'className'> & { */ 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<CommentFormProps> = ({ parentId, saveComment, title, + titleAlignment, titleLevel = 2, ...props }) => { @@ -117,7 +125,7 @@ const CommentForm: FC<CommentFormProps> = ({ {...props} > {title && ( - <Heading id={formId} level={titleLevel}> + <Heading id={formId} level={titleLevel} alignment={titleAlignment}> {title} </Heading> )} 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<PageLayoutProps> = ({ 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<NoticeKind>('info'); const [statusMessage, setStatusMessage] = useState<string>(''); @@ -175,6 +176,15 @@ const PageLayout: FC<PageLayoutProps> = ({ } }; + /** + * Check if meta properties are defined. + * + * @param {MetaData} meta - The metadata. + */ + const hasMeta = (meta: MetaData) => { + return Object.values(meta).every((value) => value === null); + }; + return ( <Layout breadcrumbSchema={breadcrumbSchema} @@ -218,7 +228,9 @@ const PageLayout: FC<PageLayoutProps> = ({ {children} </div> )} - <PageFooter meta={footerMeta} className={styles.footer} /> + {footerMeta && hasMeta(footerMeta) && ( + <PageFooter meta={footerMeta} className={styles.footer} /> + )} <Sidebar className={`${styles.sidebar} ${styles['sidebar--last']}`} aria-label={intl.formatMessage({ @@ -229,45 +241,54 @@ const PageLayout: FC<PageLayoutProps> = ({ > {widgets} </Sidebar> - {hasCommentsSection && ( - <div className={styles.comments}> - {hasComments && ( - <section className={styles.comments__section}> - <Heading level={2}>{commentsTitle}</Heading> + {allowComments && ( + <div className={styles.comments} id="comments"> + <section className={styles.comments__section}> + <Heading level={2} alignment="center"> + {commentsTitle} + </Heading> + {hasComments ? ( <CommentsList comments={comments} depth={1} Notice={ - isReplyRef.current === true ? ( + isReplyRef.current === true && ( <Notice kind={status} message={statusMessage} className={styles.notice} /> - ) : undefined + ) } saveComment={saveComment} /> - </section> - )} - {allowComments && ( - <section className={styles.comments__section}> - <CommentForm - className={styles.comments__form} - saveComment={saveComment} - title={commentFormTitle} - Notice={ - isReplyRef.current === false ? ( - <Notice - kind={status} - message={statusMessage} - className={styles.notice} - /> - ) : undefined - } - /> - </section> - )} + ) : ( + <p className={styles['comments__no-comments']}> + {intl.formatMessage({ + defaultMessage: 'No comments.', + id: 'sBwfCy', + description: 'PageLayout: no comments text', + })} + </p> + )} + </section> + <section className={styles.comments__section}> + <CommentForm + className={styles.comments__form} + saveComment={saveComment} + title={commentFormTitle} + titleAlignment="center" + Notice={ + isReplyRef.current === false && ( + <Notice + kind={status} + message={statusMessage} + className={styles.notice} + /> + ) + } + /> + </section> </div> )} </Layout> 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<ArticlePageProps> = ({ 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<ArticlePageProps> = ({ 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); } } } |
