diff options
| author | Armand Philippot <git@armandphilippot.com> | 2022-05-16 19:40:23 +0200 | 
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2022-05-16 19:40:23 +0200 | 
| commit | c77c58e18143233be042c4980a6ed08ae9beac52 (patch) | |
| tree | 94f7d828571a86470ae299fff7dffd32fb38de7c /src | |
| parent | 2155550fa36a3bc3c8f66e0926530123b4018cd4 (diff) | |
chore: adjust and complete missing styles
* add logo to topics pages and links
* add Prism styles to articles
* and a few other adjustements
Diffstat (limited to 'src')
35 files changed, 1036 insertions, 301 deletions
| diff --git a/src/components/atoms/forms/form.tsx b/src/components/atoms/forms/form.tsx index ef8dce4..b819aea 100644 --- a/src/components/atoms/forms/form.tsx +++ b/src/components/atoms/forms/form.tsx @@ -35,7 +35,6 @@ export type FormProps = {   */  const Form: FC<FormProps> = ({    children, -  className = '',    grouped = true,    onSubmit,    ...props @@ -68,7 +67,7 @@ const Form: FC<FormProps> = ({    };    return ( -    <form onSubmit={handleSubmit} className={className} {...props}> +    <form onSubmit={handleSubmit} {...props}>        {getFormItems()}      </form>    ); diff --git a/src/components/atoms/links/link.module.scss b/src/components/atoms/links/link.module.scss index 5c97bd2..1b89727 100644 --- a/src/components/atoms/links/link.module.scss +++ b/src/components/atoms/links/link.module.scss @@ -2,29 +2,6 @@  @use "@styles/abstracts/variables" as var;  .link { -  background: linear-gradient(to top, var(--color-primary) 50%, transparent 50%) -    0 0 / 100% 201% no-repeat; -  color: var(--color-primary); -  text-decoration-thickness: 0.15em; -  text-underline-offset: 20%; -  transition: all 0.3s linear 0s, text-decoration 0.18s ease-in-out 0s; - -  &:hover { -    color: var(--color-primary-light); -    text-decoration-thickness: 0.25em; -  } - -  &:focus { -    background-position: 0 100%; -    color: var(--color-fg-inverted); -  } - -  &:active { -    background-position: 0 0; -    color: var(--color-primary-dark); -    text-decoration-thickness: 18%; -  } -    &[hreflang] {      &::after {        display: inline-block; diff --git a/src/components/atoms/lists/description-list-item.module.scss b/src/components/atoms/lists/description-list-item.module.scss index 60cad57..aba90ce 100644 --- a/src/components/atoms/lists/description-list-item.module.scss +++ b/src/components/atoms/lists/description-list-item.module.scss @@ -27,6 +27,8 @@    }    &--inline-values { +    row-gap: var(--spacing-2xs); +      .term {        flex: 1 1 100%;      } diff --git a/src/components/molecules/layout/meta.tsx b/src/components/molecules/layout/meta.tsx index 1f6219a..74bd4ff 100644 --- a/src/components/molecules/layout/meta.tsx +++ b/src/components/molecules/layout/meta.tsx @@ -92,13 +92,17 @@ export type MetaData = {     */    topics?: string[] | JSX.Element[];    /** -   * A total. +   * A total number of posts.     */ -  total?: string; +  total?: number;    /**     * The update date.     */    update?: MetaDate; +  /** +   * An url. +   */ +  website?: string;  };  export type MetaKey = keyof MetaData; @@ -145,80 +149,86 @@ const Meta: FC<MetaProps> = ({        case 'author':          return intl.formatMessage({            defaultMessage: 'Written by:', -          id: 'OI0N37',            description: 'Meta: author label', +          id: 'OI0N37',          });        case 'comments':          return intl.formatMessage({            defaultMessage: 'Comments:', -          id: 'jTVIh8',            description: 'Meta: comments label', +          id: 'jTVIh8',          });        case 'creation':          return intl.formatMessage({            defaultMessage: 'Created on:', -          id: 'b4fdYE',            description: 'Meta: creation date label', +          id: 'b4fdYE',          });        case 'license':          return intl.formatMessage({            defaultMessage: 'License:', -          id: 'AuGklx',            description: 'Meta: license label', +          id: 'AuGklx',          });        case 'popularity':          return intl.formatMessage({            defaultMessage: 'Popularity:', -          id: 'pWTj2W',            description: 'Meta: popularity label', +          id: 'pWTj2W',          });        case 'publication':          return intl.formatMessage({            defaultMessage: 'Published on:', -          id: 'QGi5uD',            description: 'Meta: publication date label', +          id: 'QGi5uD',          });        case 'readingTime':          return intl.formatMessage({            defaultMessage: 'Reading time:', -          id: 'EbFvsM',            description: 'Meta: reading time label', +          id: 'EbFvsM',          });        case 'repositories':          return intl.formatMessage({            defaultMessage: 'Repositories:', -          id: 'DssFG1',            description: 'Meta: repositories label', +          id: 'DssFG1',          });        case 'technologies':          return intl.formatMessage({            defaultMessage: 'Technologies:', -          id: 'ADQmDF',            description: 'Meta: technologies label', +          id: 'ADQmDF',          });        case 'thematics':          return intl.formatMessage({            defaultMessage: 'Thematics:', -          id: 'bz53Us',            description: 'Meta: thematics label', +          id: 'bz53Us',          });        case 'topics':          return intl.formatMessage({            defaultMessage: 'Topics:', -          id: 'gJNaBD',            description: 'Meta: topics label', +          id: 'gJNaBD',          });        case 'total':          return intl.formatMessage({            defaultMessage: 'Total:', -          id: '92zgdp',            description: 'Meta: total label', +          id: '92zgdp',          });        case 'update':          return intl.formatMessage({            defaultMessage: 'Updated on:', -          id: 'tLC7bh',            description: 'Meta: update date label', +          id: 'tLC7bh', +        }); +      case 'website': +        return intl.formatMessage({ +          defaultMessage: 'Official website:', +          description: 'Meta: official website label', +          id: 'GRyyfy',          });        default:          return ''; @@ -279,8 +289,8 @@ const Meta: FC<MetaProps> = ({        {          defaultMessage:            '{commentsCount, plural, =0 {No comments} one {# comment} other {# comments}}<a11y> about {title}</a11y>', -        id: '02rgLO',          description: 'Meta: comments count', +        id: '02rgLO',        },        {          a11y: (chunks: ReactNode) => ( @@ -316,6 +326,23 @@ const Meta: FC<MetaProps> = ({        case 'publication':        case 'update':          return getDate(value as MetaDate); +      case 'total': +        return intl.formatMessage( +          { +            defaultMessage: +              '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', +            description: 'BlogPage: posts count meta', +            id: 'OF5cPz', +          }, +          { postsCount: value as number } +        ); +      case 'website': +        const url = value as string; +        return ( +          <Link href={url} external={true}> +            {url} +          </Link> +        );        default:          return value as string | ReactNode | ReactNode[];      } diff --git a/src/components/molecules/layout/page-footer.tsx b/src/components/molecules/layout/page-footer.tsx index e998b1e..97e449f 100644 --- a/src/components/molecules/layout/page-footer.tsx +++ b/src/components/molecules/layout/page-footer.tsx @@ -19,7 +19,9 @@ export type PageFooterProps = {   */  const PageFooter: FC<PageFooterProps> = ({ meta, ...props }) => {    return ( -    <footer {...props}>{meta && <Meta data={meta} layout="column" />}</footer> +    <footer {...props}> +      {meta && <Meta data={meta} withSeparator={false} />} +    </footer>    );  }; diff --git a/src/components/molecules/layout/page-header.module.scss b/src/components/molecules/layout/page-header.module.scss index 4c7df5f..232023a 100644 --- a/src/components/molecules/layout/page-header.module.scss +++ b/src/components/molecules/layout/page-header.module.scss @@ -56,3 +56,9 @@  .meta {    font-size: var(--font-size-sm);  } + +.intro { +  > *:last-child { +    margin-bottom: 0; +  } +} diff --git a/src/components/molecules/layout/page-header.tsx b/src/components/molecules/layout/page-header.tsx index 9abe9af..6759c7f 100644 --- a/src/components/molecules/layout/page-header.tsx +++ b/src/components/molecules/layout/page-header.tsx @@ -1,5 +1,5 @@  import Heading from '@components/atoms/headings/heading'; -import { FC } from 'react'; +import { FC, ReactNode } from 'react';  import Meta, { type MetaData } from './meta';  import styles from './page-header.module.scss'; @@ -19,7 +19,7 @@ export type PageHeaderProps = {    /**     * The page title.     */ -  title: string; +  title: ReactNode;  };  /** @@ -35,9 +35,12 @@ const PageHeader: FC<PageHeaderProps> = ({  }) => {    const getIntro = () => {      return typeof intro === 'string' ? ( -      <div dangerouslySetInnerHTML={{ __html: intro }} /> +      <div +        className={styles.intro} +        dangerouslySetInnerHTML={{ __html: intro }} +      />      ) : ( -      <div>{intro}</div> +      <div className={styles.intro}>{intro}</div>      );    }; diff --git a/src/components/organisms/forms/comment-form.tsx b/src/components/organisms/forms/comment-form.tsx index 9e0abdf..5ff4ea4 100644 --- a/src/components/organisms/forms/comment-form.tsx +++ b/src/components/organisms/forms/comment-form.tsx @@ -1,5 +1,5 @@  import Button from '@components/atoms/buttons/button'; -import Form from '@components/atoms/forms/form'; +import Form, { type FormProps } 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'; @@ -15,11 +15,7 @@ export type CommentFormData = {    website?: string;  }; -export type CommentFormProps = { -  /** -   * Set additional classnames to the form wrapper. -   */ -  className?: string; +export type CommentFormProps = Pick<FormProps, 'className'> & {    /**     * Pass a component to print a success/error message.     */ @@ -44,12 +40,12 @@ export type CommentFormProps = {  };  const CommentForm: FC<CommentFormProps> = ({ -  className = '',    Notice,    parentId,    saveComment,    title,    titleLevel = 2, +  ...props  }) => {    const intl = useIntl();    const [name, setName] = useState<string>(''); @@ -116,9 +112,9 @@ const CommentForm: FC<CommentFormProps> = ({    return (      <Form        onSubmit={submitHandler} -      className={className}        aria-label={formAriaLabel}        aria-labelledby={formLabelledBy} +      {...props}      >        {title && (          <Heading id={formId} level={titleLevel}> diff --git a/src/components/organisms/widgets/sharing.stories.tsx b/src/components/organisms/widgets/sharing.stories.tsx index 47213b6..59b86d3 100644 --- a/src/components/organisms/widgets/sharing.stories.tsx +++ b/src/components/organisms/widgets/sharing.stories.tsx @@ -1,5 +1,4 @@  import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { IntlProvider } from 'react-intl';  import SharingWidget from './sharing';  /** @@ -9,6 +8,19 @@ export default {    title: 'Organisms/Widgets',    component: SharingWidget,    argTypes: { +    className: { +      control: { +        type: 'text', +      }, +      description: 'Set additional classnames to the sharing links list.', +      table: { +        category: 'Styles', +      }, +      type: { +        name: 'string', +        required: false, +      }, +    },      data: {        description: 'The page data.',        type: { @@ -58,13 +70,6 @@ export default {        },      },    }, -  decorators: [ -    (Story) => ( -      <IntlProvider locale="en"> -        <Story /> -      </IntlProvider> -    ), -  ],  } as ComponentMeta<typeof SharingWidget>;  const Template: ComponentStory<typeof SharingWidget> = (args) => ( diff --git a/src/components/organisms/widgets/sharing.tsx b/src/components/organisms/widgets/sharing.tsx index 85dadb0..c63f5db 100644 --- a/src/components/organisms/widgets/sharing.tsx +++ b/src/components/organisms/widgets/sharing.tsx @@ -23,6 +23,10 @@ export type SharingData = {  export type SharingProps = {    /** +   * Set additional classnames to the sharing links list. +   */ +  className?: string; +  /**     * The page data to share.     */    data: SharingData; @@ -46,6 +50,7 @@ export type SharingProps = {   * Render a list of sharing links inside a widget.   */  const Sharing: FC<SharingProps> = ({ +  className = '',    data,    media,    expanded = true, @@ -201,7 +206,7 @@ const Sharing: FC<SharingProps> = ({    return (      <Widget expanded={expanded} level={level} title={widgetTitle} {...props}> -      <ul className={styles.list}>{getItems()}</ul> +      <ul className={`${styles.list} ${className}`}>{getItems()}</ul>      </Widget>    );  }; diff --git a/src/components/templates/page/page-layout.module.scss b/src/components/templates/page/page-layout.module.scss index 7602492..83e5a80 100644 --- a/src/components/templates/page/page-layout.module.scss +++ b/src/components/templates/page/page-layout.module.scss @@ -72,6 +72,7 @@  .footer {    grid-column: 2; +  margin: var(--spacing-sm) 0 var(--spacing-2xs);  }  .comments { @@ -87,4 +88,9 @@      grid-column: 2;      margin: var(--spacing-md) 0 0;    } + +  &__form { +    max-width: 40ch; +    margin: auto; +  }  } diff --git a/src/components/templates/page/page-layout.stories.tsx b/src/components/templates/page/page-layout.stories.tsx index 002b951..8af5f98 100644 --- a/src/components/templates/page/page-layout.stories.tsx +++ b/src/components/templates/page/page-layout.stories.tsx @@ -473,7 +473,7 @@ export const Blog = Template.bind({});  Blog.args = {    breadcrumb: postsListBreadcrumb,    title: 'Blog', -  headerMeta: { total: `${posts.length} posts` }, +  headerMeta: { total: posts.length },    children: (      <>        <PostsList posts={posts} byYear={true} total={posts.length} /> diff --git a/src/components/templates/page/page-layout.tsx b/src/components/templates/page/page-layout.tsx index bc90f4c..f3f3ea8 100644 --- a/src/components/templates/page/page-layout.tsx +++ b/src/components/templates/page/page-layout.tsx @@ -20,7 +20,7 @@ import TableOfContents from '@components/organisms/widgets/table-of-contents';  import { type SendCommentVars } from '@services/graphql/api';  import { sendComment } from '@services/graphql/comments';  import useIsMounted from '@utils/hooks/use-is-mounted'; -import { FC, ReactNode, useRef, useState } from 'react'; +import { FC, HTMLAttributes, ReactNode, useRef, useState } from 'react';  import { useIntl } from 'react-intl';  import Layout, { type LayoutProps } from '../layout/layout';  import styles from './page-layout.module.scss'; @@ -33,6 +33,11 @@ export type PageLayoutProps = Pick<     * True if the page accepts new comments. Default: false.     */    allowComments?: boolean; +  bodyAttributes?: HTMLAttributes<HTMLDivElement>; +  /** +   * Set additional classnames to the body wrapper. +   */ +  bodyClassName?: string;    /**     * The breadcrumb items.     */ @@ -83,6 +88,8 @@ export type PageLayoutProps = Pick<  const PageLayout: FC<PageLayoutProps> = ({    children,    allowComments = false, +  bodyAttributes, +  bodyClassName = '',    breadcrumb,    breadcrumbSchema,    comments, @@ -91,8 +98,8 @@ const PageLayout: FC<PageLayoutProps> = ({    id,    intro,    isHome = false, -  widgets,    title, +  widgets,    withToC = false,  }) => {    const intl = useIntl(); @@ -202,11 +209,12 @@ const PageLayout: FC<PageLayoutProps> = ({        {typeof children === 'string' ? (          <div            ref={bodyRef} -          className={styles.body} +          className={`${styles.body} ${bodyClassName}`}            dangerouslySetInnerHTML={{ __html: children }} +          {...bodyAttributes}          />        ) : ( -        <div ref={bodyRef} className={styles.body}> +        <div ref={bodyRef} className={`${styles.body} ${bodyClassName}`}>            {children}          </div>        )} @@ -245,6 +253,7 @@ const PageLayout: FC<PageLayoutProps> = ({            {allowComments && (              <section className={styles.comments__section}>                <CommentForm +                className={styles.comments__form}                  saveComment={saveComment}                  title={commentFormTitle}                  Notice={ diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx index 5eeabd9..995e3a9 100644 --- a/src/pages/article/[slug].tsx +++ b/src/pages/article/[slug].tsx @@ -1,5 +1,6 @@  import ButtonLink from '@components/atoms/buttons/button-link';  import Link from '@components/atoms/links/link'; +import ResponsiveImage from '@components/molecules/images/responsive-image';  import Sharing from '@components/organisms/widgets/sharing';  import PageLayout, {    type PageLayoutProps, @@ -9,15 +10,20 @@ import {    getArticleBySlug,  } from '@services/graphql/articles';  import { getPostComments } from '@services/graphql/comments'; +import styles from '@styles/pages/article.module.scss';  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 useSettings from '@utils/hooks/use-settings';  import { GetStaticPaths, GetStaticProps, NextPage } from 'next';  import Head from 'next/head';  import { useRouter } from 'next/router';  import Script from 'next/script';  import { ParsedUrlQuery } from 'querystring'; +import { HTMLAttributes } from 'react'; +import { useIntl } from 'react-intl';  import { Blog, BlogPosting, Graph, WebPage } from 'schema-dts';  import useSWR from 'swr'; @@ -54,16 +60,31 @@ const ArticlePage: NextPage<ArticlePageProps> = ({ comments, post }) => {        )),    }; +  const intl = useIntl(); +  const footerMetaLabel = intl.formatMessage({ +    defaultMessage: 'Read more articles about:', +    description: 'ArticlePage: footer topics list label', +    id: '50xc4o', +  }); +    const footerMeta: PageLayoutProps['footerMeta'] = { -    topics: -      topics && -      topics.map((topic) => { +    custom: topics && { +      label: footerMetaLabel, +      value: topics.map((topic) => {          return ( -          <ButtonLink key={topic.id} target={`/sujet/${topic.slug}`}> +          <ButtonLink +            key={topic.id} +            target={`/sujet/${topic.slug}`} +            className={styles.btn} +          > +            {topic.logo && ( +              <ResponsiveImage className={styles.btn__icon} {...topic.logo} /> +            )}{' '}              {topic.name}            </ButtonLink>          );        }), +    },    };    const { website } = useSettings(); @@ -156,6 +177,15 @@ const ArticlePage: NextPage<ArticlePageProps> = ({ comments, post }) => {      });    }; +  const prismPlugins: PrismPlugin[] = ['command-line', 'line-numbers']; +  const { pluginsAttribute, pluginsClassName } = usePrismPlugins(prismPlugins); +  useAddPrismClassAttr({ +    attributes: { +      'data-filter-output': '#output#"', +    }, +    classNames: pluginsClassName, +  }); +    return (      <>        <Head> @@ -173,6 +203,10 @@ const ArticlePage: NextPage<ArticlePageProps> = ({ comments, post }) => {        />        <PageLayout          allowComments={true} +        bodyAttributes={{ +          ...(pluginsAttribute as HTMLAttributes<HTMLDivElement>), +        }} +        bodyClassName={styles.body}          breadcrumb={breadcrumbItems}          breadcrumbSchema={breadcrumbSchema}          comments={data && getCommentsList(data)} @@ -185,6 +219,7 @@ const ArticlePage: NextPage<ArticlePageProps> = ({ comments, post }) => {          widgets={[            <Sharing              key="sharing-widget" +            className={styles.widget}              data={{ excerpt: intro, title, url: pageUrl }}              media={[                'diaspora', diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx index a5ef045..b6ce221 100644 --- a/src/pages/blog/index.tsx +++ b/src/pages/blog/index.tsx @@ -114,16 +114,6 @@ const BlogPage: NextPage<BlogPageProps> = ({      '@graph': [webpageSchema, blogSchema],    }; -  const postsCount = intl.formatMessage( -    { -      defaultMessage: -        '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', -      id: 'OF5cPz', -      description: 'BlogPage: posts count meta', -    }, -    { postsCount: totalArticles } -  ); -    /**     * Retrieve the formatted meta.     * @@ -231,7 +221,7 @@ const BlogPage: NextPage<BlogPageProps> = ({          title={title}          breadcrumb={breadcrumbItems}          breadcrumbSchema={breadcrumbSchema} -        headerMeta={{ total: postsCount }} +        headerMeta={{ total: totalArticles }}          widgets={[            <LinksListWidget              key="thematics-list" diff --git a/src/pages/cv.tsx b/src/pages/cv.tsx index b3dec10..7936c84 100644 --- a/src/pages/cv.tsx +++ b/src/pages/cv.tsx @@ -1,4 +1,6 @@ +import Heading from '@components/atoms/headings/heading';  import Link from '@components/atoms/links/link'; +import List from '@components/atoms/lists/list';  import ImageWidget from '@components/organisms/widgets/image-widget';  import SocialMedia from '@components/organisms/widgets/social-media';  import PageLayout, { @@ -9,11 +11,12 @@ import styles from '@styles/pages/cv.module.scss';  import { loadTranslation } from '@utils/helpers/i18n';  import useBreadcrumb from '@utils/hooks/use-breadcrumb';  import useSettings from '@utils/hooks/use-settings'; +import { NestedMDXComponents } from 'mdx/types';  import { GetStaticProps, NextPage } from 'next';  import Head from 'next/head';  import { useRouter } from 'next/router';  import Script from 'next/script'; -import { ReactNode } from 'react'; +import React, { ReactNode } from 'react';  import { useIntl } from 'react-intl';  import { AboutPage, Graph, WebPage } from 'schema-dts'; @@ -141,6 +144,18 @@ const CVPage: NextPage = () => {      '@graph': [webpageSchema, cvSchema],    }; +  const components: NestedMDXComponents = { +    a: (props) => <Link external={true} {...props} />, +    h1: (props) => <Heading level={1} {...props} />, +    h2: (props) => <Heading level={2} {...props} />, +    h3: (props) => <Heading level={3} {...props} />, +    h4: (props) => <Heading level={4} {...props} />, +    h5: (props) => <Heading level={5} {...props} />, +    h6: (props) => <Heading level={6} {...props} />, +    Link: (props) => <Link {...props} />, +    List: (props) => <List {...props} />, +  }; +    return (      <PageLayout        breadcrumb={breadcrumbItems} @@ -166,7 +181,7 @@ const CVPage: NextPage = () => {          type="application/ld+json"          dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}        /> -      <CVContent /> +      <CVContent components={components} />      </PageLayout>    );  }; diff --git a/src/pages/recherche/index.tsx b/src/pages/recherche/index.tsx index 0a7dc60..d88a293 100644 --- a/src/pages/recherche/index.tsx +++ b/src/pages/recherche/index.tsx @@ -140,16 +140,6 @@ const SearchPage: NextPage<SearchPageProps> = ({      getTotalArticles(query.s as string)    ); -  const postsCount = intl.formatMessage( -    { -      defaultMessage: -        '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}', -      id: 'LtsVOx', -      description: 'SearchPage: posts count meta', -    }, -    { postsCount: totalArticles || 0 } -  ); -    /**     * Retrieve the formatted meta.     * @@ -244,7 +234,7 @@ const SearchPage: NextPage<SearchPageProps> = ({          title={title}          breadcrumb={breadcrumbItems}          breadcrumbSchema={breadcrumbSchema} -        headerMeta={{ total: postsCount }} +        headerMeta={{ total: totalArticles }}          widgets={[            <LinksListWidget              key="thematics-list" diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx index 447d969..348fe05 100644 --- a/src/pages/sujet/[slug].tsx +++ b/src/pages/sujet/[slug].tsx @@ -1,4 +1,5 @@  import Heading from '@components/atoms/headings/heading'; +import ResponsiveImage from '@components/molecules/images/responsive-image';  import PostsList, { type Post } from '@components/organisms/layout/posts-list';  import LinksListWidget from '@components/organisms/widgets/links-list-widget';  import PageLayout, { @@ -10,6 +11,7 @@ import {    getTopicsPreview,    getTotalTopics,  } from '@services/graphql/topics'; +import styles from '@styles/pages/topic.module.scss';  import { type Article, type PageLink, type Topic } from '@ts/types/app';  import { loadTranslation, type Messages } from '@utils/helpers/i18n';  import { @@ -35,7 +37,14 @@ export type TopicPageProps = {  const TopicPage: NextPage<TopicPageProps> = ({ currentTopic, topics }) => {    const { content, intro, meta, slug, title } = currentTopic; -  const { articles, dates, seo, thematics } = meta; +  const { +    articles, +    cover, +    dates, +    seo, +    thematics, +    website: officialWebsite, +  } = meta;    const intl = useIntl();    const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({      title, @@ -45,6 +54,8 @@ const TopicPage: NextPage<TopicPageProps> = ({ currentTopic, topics }) => {    const headerMeta: PageLayoutProps['headerMeta'] = {      publication: { date: dates.publication },      update: dates.update ? { date: dates.update } : undefined, +    website: officialWebsite, +    total: articles ? articles.length : undefined,    };    const { website } = useSettings(); @@ -98,10 +109,10 @@ const TopicPage: NextPage<TopicPageProps> = ({ currentTopic, topics }) => {          ...remainingData        } = article; -      const { cover, ...remainingMeta } = articleMeta; +      const { cover: articleCover, ...remainingMeta } = articleMeta;        return { -        cover, +        cover: articleCover,          excerpt: articleIntro,          meta: getPostMeta(remainingMeta),          url: `/article/${articleSlug}`, @@ -122,6 +133,15 @@ const TopicPage: NextPage<TopicPageProps> = ({ currentTopic, topics }) => {      id: '/sRqPT',    }); +  const getPageHeading = () => { +    return ( +      <> +        {cover && <ResponsiveImage className={styles.logo} {...cover} />} +        {title} +      </> +    ); +  }; +    return (      <>        <Head> @@ -140,7 +160,7 @@ const TopicPage: NextPage<TopicPageProps> = ({ currentTopic, topics }) => {        <PageLayout          breadcrumb={breadcrumbItems}          breadcrumbSchema={breadcrumbSchema} -        title={title} +        title={getPageHeading()}          intro={intro}          headerMeta={headerMeta}          widgets={ @@ -206,14 +226,15 @@ export const getStaticProps: GetStaticProps<TopicPageProps> = async ({    const allTopics = allTopicsEdges.edges.map((edge) =>      getPageLinkFromRawData(edge.node)    ); +  const topicsLinks = allTopics.filter( +    (topic) => topic.slug !== (params!.slug as TopicParams['slug']) +  );    const translation = await loadTranslation(locale);    return {      props: {        currentTopic: JSON.parse(JSON.stringify(currentTopic)), -      topics: allTopics.filter( -        (topic) => topic.slug !== (params!.slug as TopicParams['slug']) -      ), +      topics: JSON.parse(JSON.stringify(topicsLinks)),        translation,      },    }; diff --git a/src/services/graphql/articles.query.ts b/src/services/graphql/articles.query.ts index d65bc9e..e02ca8e 100644 --- a/src/services/graphql/articles.query.ts +++ b/src/services/graphql/articles.query.ts @@ -14,6 +14,17 @@ export const articleBySlugQuery = `query PostBy($slug: ID!) {        postsInTopic {          ... on Topic {            databaseId +          featuredImage { +            node { +              altText +              mediaDetails { +                height +                width +              } +              sourceUrl +              title +            } +          }            slug            title          } diff --git a/src/services/graphql/topics.query.ts b/src/services/graphql/topics.query.ts index 19be5d7..4574256 100644 --- a/src/services/graphql/topics.query.ts +++ b/src/services/graphql/topics.query.ts @@ -94,6 +94,17 @@ export const topicsListQuery = `query TopicsList($after: String = "", $first: In        cursor        node {          databaseId +        featuredImage { +          node { +            altText +            mediaDetails { +              height +              width +            } +            sourceUrl +            title +          } +        }          slug          title        } diff --git a/src/styles/base/_typography.scss b/src/styles/base/_typography.scss index 7b7a695..2c3c8cc 100644 --- a/src/styles/base/_typography.scss +++ b/src/styles/base/_typography.scss @@ -120,21 +120,15 @@ dl {    & & {      margin: var(--spacing-2xs) 0 0;    } -} - -dt { -  flex: 0 0 max-content; -  font-weight: 600; -} -dd { -  flex: 0 0 auto; -  margin: 0; +  ::marker { +    color: var(--color-primary-dark); +  }  }  a {    background: linear-gradient(to top, var(--color-primary) 50%, transparent 50%) -    0 0 / 100% 200% no-repeat; +    0 0 / 100% 201% no-repeat;    color: var(--color-primary);    text-decoration-thickness: 0.15em;    text-underline-offset: 20%; @@ -199,13 +193,3 @@ pre {    word-break: normal;    word-wrap: normal;  } - -figure { -  margin: var(--spacing-md) 0; -} - -figcaption { -  margin-top: var(--spacing-xs); -  font-size: var(--font-size-sm); -  text-align: center; -} diff --git a/src/styles/components/_wp-blocks.scss b/src/styles/components/_wp-blocks.scss deleted file mode 100644 index efd6db5..0000000 --- a/src/styles/components/_wp-blocks.scss +++ /dev/null @@ -1,166 +0,0 @@ -@use "@styles/abstracts/functions" as fun; -@use "@styles/abstracts/mixins" as mix; -@use "@styles/abstracts/placeholders"; - -.wp-block-quote { -  margin: var(--spacing-sm) 0; -  padding: var(--spacing-sm); -  position: relative; -  border: fun.convert-px(1) solid var(--color-primary-lighter); -  border-left: fun.convert-px(5) solid var(--color-primary-lighter); -  box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow), -    fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0 -      var(--color-shadow-light), -    fun.convert-px(3) fun.convert-px(3) fun.convert-px(6) 0 -      var(--color-shadow-light); -  font-style: italic; - -  > *:last-child { -    margin: 0; -  } - -  cite { -    font-size: var(--font-size-sm); -    font-style: normal; -    font-weight: 600; -  } -} - -.wp-block-code, -.wp-block-preformatted { -  margin: 0 auto var(--spacing-md); -  padding: var(--spacing-xs) var(--spacing-sm); -  background: var(--color-bg-secondary); -  border: fun.convert-px(1) solid var(--color-border-light); -  color: var(--color-fg); -} - -.wp-block-columns { -  display: grid; -  grid-template-columns: minmax(0, 1fr); -  gap: var(--spacing-md); -  margin: var(--spacing-md) 0; - -  @include mix.media("screen") { -    @include mix.dimensions("sm") { -      grid-template-columns: repeat(2, minmax(0, 1fr)); -    } -  } - -  &.are-vertically-aligned-center { -    align-items: center; -  } -} - -.wp-block-column { -  > *:first-child { -    margin-top: 0; -  } - -  > *:last-child { -    margin-bottom: 0; -  } -} - -.wp-block-gallery { -  display: grid; -  grid-template-columns: minmax(0, 1fr); -  gap: var(--spacing-sm); - -  .blocks-gallery-grid { -    @extend %reset-list; - -    grid-column: 1 / -1; -    grid-row: 1 / -1; -    display: grid; -    grid-template-columns: minmax(0, 1fr); -    gap: var(--spacing-sm); -  } - -  .blocks-gallery-item { -    figure { -      margin: 0; -    } - -    a { -      display: block; -      box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow), -        fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0 -          var(--color-shadow-light), -        fun.convert-px(3) fun.convert-px(3) fun.convert-px(6) 0 -          var(--color-shadow-light); - -      &:hover, -      &:focus { -        transform: scale(1.05); -        box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow), -          fun.convert-px(3) fun.convert-px(3) fun.convert-px(2) 0 -            var(--color-shadow-light), -          fun.convert-px(5) fun.convert-px(5) fun.convert-px(8) 0 -            var(--color-shadow-light); -      } - -      &:focus { -        outline: solid var(--color-primary-light); -      } - -      &:active { -        transform: scale(0.95); -        box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow), -          fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0 -            var(--color-shadow-light), -          0 0 0 0 var(--color-shadow-light); -        outline: none; -      } -    } -  } - -  &.aligncenter { -    .blocks-gallery-grid { -      align-items: center; -    } -  } - -  @for $i from 0 to 6 { -    &.columns-#{$i} { -      @include mix.media("screen") { -        @include mix.dimensions("xs") { -          grid-template-columns: repeat(2, minmax(0, 1fr)); - -          .blocks-gallery-grid { -            grid-template-columns: repeat(2, minmax(0, 1fr)); -          } -        } - -        @include mix.dimensions("sm") { -          grid-template-columns: repeat(#{$i}, minmax(0, 1fr)); - -          .blocks-gallery-grid { -            grid-template-columns: repeat(3, minmax(0, 1fr)); -          } -        } -      } -    } -  } -} - -.wp-block-image { -  img { -    display: block; -    margin: auto; -    box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow), -      fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0 -        var(--color-shadow-light), -      fun.convert-px(3) fun.convert-px(3) fun.convert-px(6) 0 -        var(--color-shadow-light); -    text-align: center; -  } -} - -.wp-block-video { -  box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow), -    fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0 -      var(--color-shadow-light), -    fun.convert-px(3) fun.convert-px(3) fun.convert-px(6) 0 -      var(--color-shadow-light); -} diff --git a/src/styles/globals.scss b/src/styles/globals.scss index f9a1281..8ece909 100644 --- a/src/styles/globals.scss +++ b/src/styles/globals.scss @@ -6,7 +6,6 @@   * Import each files separately to define vendors styles order.   */  @use "modern-normalize"; -@use "vendors/prism";  /**   * 2.0. Base @@ -22,14 +21,7 @@  @use "base/typography";  /** - * 3.0. Components - * - * Define styles for external components (like WordPress blocks). - */ -@use "components/wp-blocks"; - -/** - * 4.0. Themes + * 3.0. Themes   *   * Define themes specific styles.   */ diff --git a/src/styles/pages/article.module.scss b/src/styles/pages/article.module.scss new file mode 100644 index 0000000..a42c633 --- /dev/null +++ b/src/styles/pages/article.module.scss @@ -0,0 +1,36 @@ +@use "@styles/abstracts/functions" as fun; +@use "@styles/abstracts/mixins" as mix; +@use "@styles/abstracts/variables" as var; +@use "partials/article-links"; +@use "partials/article-lists"; +@use "partials/article-media"; +@use "partials/article-prism"; +@use "partials/article-wp-blocks"; + +.btn { +  margin-right: var(--spacing-2xs); +  padding: var(--spacing-2xs) var(--spacing-xs); + +  &__icon { +    max-width: fun.convert-px(22); +    margin-right: var(--spacing-2xs); +  } +} + +.body { +  :global { +    @include article-links.styles; +    @include article-lists.styles; +    @include article-media.styles; +    @include article-prism.styles; +    @include article-wp-blocks.styles; +  } +} + +.widget { +  @include mix.media("screen") { +    @include mix.dimensions("md") { +      width: min-content; +    } +  } +} diff --git a/src/styles/pages/partials/_article-headings.scss b/src/styles/pages/partials/_article-headings.scss new file mode 100644 index 0000000..c0c3519 --- /dev/null +++ b/src/styles/pages/partials/_article-headings.scss @@ -0,0 +1,57 @@ +@use "@styles/abstracts/functions" as fun; + +@mixin styles { +  h1 { +    font-size: var(--font-size-3xl); +    font-weight: 500; +  } + +  h2 { +    padding-bottom: fun.convert-px(3); +    background: linear-gradient( +        to top, +        var(--color-primary-dark) 0.3rem, +        transparent 0.3rem +      ) +      0 0 / 3rem 100% no-repeat; +    font-size: var(--font-size-2xl); +    font-weight: 500; +    text-shadow: fun.convert-px(1) fun.convert-px(1) 0 var(--color-shadow-light); +  } + +  h3 { +    font-size: var(--font-size-xl); +    font-weight: 500; +  } + +  h4 { +    font-size: var(--font-size-lg); +    font-weight: 500; +  } + +  h5 { +    font-size: var(--font-size-md); +    font-weight: 600; +  } + +  h6 { +    font-size: var(--font-size-md); +    font-weight: 500; +  } + +  h1, +  h2, +  h3, +  h4, +  h5, +  h6 { +    color: var(--color-primary-dark); +    font-family: var(--font-family-secondary); +    letter-spacing: 0.01ex; +    margin: 0 0 var(--spacing-sm); + +    & + & { +      margin-top: var(--spacing-md); +    } +  } +} diff --git a/src/styles/pages/partials/_article-links.scss b/src/styles/pages/partials/_article-links.scss new file mode 100644 index 0000000..df86dcf --- /dev/null +++ b/src/styles/pages/partials/_article-links.scss @@ -0,0 +1,104 @@ +@use "@styles/abstracts/functions" as fun; +@use "@styles/abstracts/variables" as var; + +@mixin styles { +  a { +    &[hreflang] { +      &::after { +        display: inline-block; + +        /* Prettier is removing spacing between content parts. */ + +        /* prettier-ignore */ +        content: "\0000a0[" attr(hreflang) "]"; +        font-size: var(--font-size-sm); +      } +    } +  } + +  a.download { +    &::after { +      display: inline-block; + +      /* Prettier is removing spacing between content parts. */ + +      /* prettier-ignore */ +      content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>')); +    } + +    &:focus:not(:active)::after { +      /* Prettier is removing spacing between content parts. */ + +      /* prettier-ignore */ +      content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_white}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>')); +    } + +    &[hreflang] { +      &::after { +        /* Prettier is removing spacing between content parts. */ + +        /* prettier-ignore */ +        content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>')); +      } +    } +  } + +  a.external { +    &::after { +      display: inline-block; + +      /* Prettier is removing spacing between content parts. */ + +      /* prettier-ignore */ +      content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>')); +    } + +    &:focus:not(:active)::after { +      /* Prettier is removing spacing between content parts. */ + +      /* prettier-ignore */ +      content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_white}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>')); +    } + +    &[hreflang] { +      &::after { +        /* Prettier is removing spacing between content parts. */ + +        /* prettier-ignore */ +        content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>')); +      } + +      &:focus:not(:active)::after { +        /* Prettier is removing spacing between content parts. */ + +        /* prettier-ignore */ +        content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_white}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>')); +      } +    } +  } + +  a.external.download { +    &::after { +      /* Prettier is removing spacing between content parts. */ + +      /* prettier-ignore */ +      content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>')) "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>')); +    } + +    &[hreflang] { +      &::after { +        /* Prettier is removing spacing between content parts. */ + +        /* prettier-ignore */ +        content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>')) "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>')); +      } + +      &:focus:not(:active)::after { +        /* Prettier is removing spacing between content parts. */ + +        /* prettier-ignore */ +        content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>')) "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_white}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>')); +      } +    } +  } +} diff --git a/src/styles/pages/partials/_article-lists.scss b/src/styles/pages/partials/_article-lists.scss new file mode 100644 index 0000000..c0084b0 --- /dev/null +++ b/src/styles/pages/partials/_article-lists.scss @@ -0,0 +1,65 @@ +@mixin styles { +  ol { +    padding: 0; +    list-style-type: none; +    counter-reset: li; + +    > li { +      display: table; +      counter-increment: li; + +      &::before { +        content: counters(li, ".") ". "; +        display: table-cell; +        padding-right: var(--spacing-2xs); +        color: var(--color-secondary); +      } +    } + +    li ol > li::before { +      content: counters(li, ".") ". "; +    } +  } + +  ul, +  ol { +    li:not(:last-child) { +      margin-bottom: var(--spacing-2xs); +    } + +    ::marker { +      color: var(--color-primary-dark); +    } +  } + +  ul { +    padding-left: var(--spacing-sm); +  } + +  dl { +    display: flex; +    flex-flow: row wrap; +    gap: var(--spacing-2xs); +    width: fit-content; +  } + +  ul, +  ol, +  dl { +    margin: var(--spacing-sm) 0; + +    & & { +      margin: var(--spacing-2xs) 0 0; +    } +  } + +  dt { +    color: var(--color-fg-light); +    font-weight: 600; +  } + +  dd { +    margin: 0; +    word-break: break-all; +  } +} diff --git a/src/styles/pages/partials/_article-media.scss b/src/styles/pages/partials/_article-media.scss new file mode 100644 index 0000000..8359881 --- /dev/null +++ b/src/styles/pages/partials/_article-media.scss @@ -0,0 +1,11 @@ +@mixin styles { +  figure { +    margin: var(--spacing-md) 0; +  } + +  figcaption { +    margin-top: var(--spacing-xs); +    font-size: var(--font-size-sm); +    text-align: center; +  } +} diff --git a/src/styles/pages/partials/_article-prism.scss b/src/styles/pages/partials/_article-prism.scss new file mode 100644 index 0000000..025e0c0 --- /dev/null +++ b/src/styles/pages/partials/_article-prism.scss @@ -0,0 +1,301 @@ +@use "@styles/abstracts/functions" as fun; +@use "@styles/abstracts/mixins" as mix; + +@mixin styles { +  .code-toolbar { +    --gutter-size: clamp(#{fun.convert-px(75)}, 20vw, #{fun.convert-px(90)}); +    --toolbar-height: #{fun.convert-px(90)}; + +    position: relative; +    margin-top: calc(var(--toolbar-height) + var(--spacing-md)); + +    @include mix.media("screen") { +      @include mix.dimensions("2xs") { +        --toolbar-height: #{fun.convert-px(60)}; +      } +    } + +    .toolbar { +      display: grid; +      grid-template-columns: max-content minmax(0, 1fr); +      justify-items: end; +      width: 100%; +      height: var(--toolbar-height); +      position: absolute; +      top: calc(var(--toolbar-height) * -1); +      left: 0; +      right: 0; +      background: var(--color-bg-tertiary); +      border: fun.convert-px(1) solid var(--color-border); + +      @include mix.media("screen") { +        @include mix.dimensions("2xs") { +          display: flex; +          flex-flow: row wrap; +        } +      } +    } + +    .toolbar-item { +      display: flex; +      align-items: center; +    } + +    .toolbar-item:nth-child(1) { +      grid-column: 1; +      grid-row: 1 / 3; +      margin-right: auto; +      padding: 0 var(--spacing-sm); +      background: var(--color-bg-code); +      border-right: fun.convert-px(1) solid var(--color-border); +      color: var(--color-primary-darker); +      font-size: var(--font-size-sm); +      font-weight: 600; +    } + +    .toolbar-item:nth-child(2) { +      grid-column: 2; +      grid-row: 1; +      margin: 0 var(--spacing-2xs); +    } + +    .toolbar-item:nth-child(3) { +      grid-column: 2; +      grid-row: 2; +      margin: 0 var(--spacing-2xs); +    } +  } + +  pre[class*="language-"] { +    max-height: max(30vw, fun.convert-px(300)); +    margin: var(--spacing-md) 0; +    padding: 0; +    position: relative; +    background: var(--color-bg-secondary); +    color: var(--color-fg); +    border: fun.convert-px(1) solid var(--color-border); + +    > code { +      display: block; +      padding: var(--spacing-xs) 0 var(--spacing-xs) +        calc(var(--gutter-size) + var(--spacing-xs)); +    } + +    .line-numbers-rows, +    .command-line-prompt { +      width: var(--gutter-size); +      min-height: 100%; +      padding: var(--spacing-xs) var(--spacing-2xs); +      position: absolute; +      top: 0; +      left: 0; +      pointer-events: none; +      user-select: none; +      background: var(--color-bg); +      border-right: fun.convert-px(1) solid var(--color-border); +    } + +    .token { +      &.comment, +      &.doc-comment { +        color: var(--color-fg-light); +      } + +      &.punctuation { +        color: var(--color-fg); +      } + +      &.attr-name, +      &.hexcode, +      &.inserted, +      &.string { +        color: var(--color-token-green); +      } + +      &.class, +      &.coord, +      &.id, +      &.function { +        color: var(--color-token-purple); +      } + +      &.builtin, +      &.builtin.class-name, +      &.property-access, +      &.regex, +      &.scope { +        color: var(--color-token-magenta); +      } + +      &.class-name, +      &.constant, +      &.global, +      &.interpolation, +      &.key, +      &.package, +      &.this, +      &.title, +      &.variable { +        color: var(--color-token-blue); +      } + +      &.combinator, +      &.keyword, +      &.operator, +      &.pseudo-class, +      &.pseudo-element, +      &.rule, +      &.selector, +      &.unit { +        color: var(--color-token-orange); +      } + +      &.attr-value, +      &.boolean, +      &.number { +        color: var(--color-token-yellow); +      } + +      &.delimiter, +      &.doctype, +      &.parameter, +      &.parent, +      &.property, +      &.shebang, +      &.tag { +        color: var(--color-token-cyan); +      } + +      &.deleted { +        color: var(--color-token-red); +      } + +      &.punctuation.brace-hover, +      &.punctuation.brace-selected { +        background: var(--color-bg); +        outline: solid fun.convert-px(1) var(--color-primary-light); +      } +    } + +    span.inline-color-wrapper { +      background: url(fun.encode-svg( +        '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="gray" d="M0 0h2v2H0z"/><path fill="white" d="M0 0h1v1H0zM1 1h1v1H1z"/></svg>' +      )); + +      /* Prevent glitches where 1px from the repeating pattern could be seen. */ +      background-position: center; +      background-size: 110%; + +      display: inline-block; +      height: 1.1ch; +      width: 1.1ch; +      margin: 0 0.5ch 0 0; +      border: fun.convert-px(1) solid var(--color-bg); +      outline: fun.convert-px(1) solid var(--color-border-dark); +      overflow: hidden; +    } + +    span.inline-color { +      display: block; + +      /* To prevent visual glitches again */ +      height: 120%; +      width: 120%; +    } +  } + +  pre.line-numbers { +    counter-reset: lineNumber; + +    .line-numbers-rows { +      > span { +        counter-increment: lineNumber; + +        &::before { +          display: block; +          padding: 0 var(--spacing-xs); +          content: counter(lineNumber); +          color: var(--color-primary-darker); +          text-align: right; +          line-height: var(--line-height); +        } +      } +    } +  } + +  pre.command-line { +    --gutter-size: clamp(#{fun.convert-px(195)}, 48vw, #{fun.convert-px(235)}); + +    ~ .toolbar { +      --gutter-size: clamp( +        #{fun.convert-px(195)}, +        48vw, +        #{fun.convert-px(235)} +      ); +    } + +    .command-line-prompt { +      > span { +        &::before { +          display: block; +          content: ""; +        } + +        &[data-user]::before { +          content: "[" attr(data-user) "@" attr(data-host) "] $"; +        } + +        &[data-user="root"]::before { +          content: "[" attr(data-user) "@" attr(data-host) "] #"; +        } + +        &[data-prompt]::before { +          content: attr(data-prompt); +        } +      } +    } +  } + +  .copy-to-clipboard-button, +  .prism-color-scheme-button { +    display: block; +    padding: fun.convert-px(3) var(--spacing-xs); +    background: var(--color-bg); +    border: 0.4ex solid var(--color-primary); +    border-radius: fun.convert-px(30); +    box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) +        var(--color-shadow), +      fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-2) +        var(--color-shadow), +      fun.convert-px(3) fun.convert-px(4) fun.convert-px(5) fun.convert-px(-4) +        var(--color-shadow); +    color: var(--color-primary); +    font-size: var(--font-size-sm); +    font-weight: 600; +    transition: all 0.35s ease-in-out 0s; + +    &:hover, +    &:focus { +      transform: translateX(#{fun.convert-px(-2)}) +        translateY(#{fun.convert-px(-2)}); +      box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) +          var(--color-shadow-light), +        fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-2) +          var(--color-shadow-light), +        fun.convert-px(3) fun.convert-px(4) fun.convert-px(5) fun.convert-px(-4) +          var(--color-shadow-light), +        fun.convert-px(4) fun.convert-px(7) fun.convert-px(8) fun.convert-px(-3) +          var(--color-shadow-light); +    } + +    &:focus { +      text-decoration: underline var(--color-primary) fun.convert-px(3); +    } + +    &:active { +      text-decoration: none; +      transform: translateY(#{fun.convert-px(2)}); +      box-shadow: 0 0 0 0 var(--color-shadow); +    } +  } +} diff --git a/src/styles/pages/partials/_article-wp-blocks.scss b/src/styles/pages/partials/_article-wp-blocks.scss new file mode 100644 index 0000000..d4fed5a --- /dev/null +++ b/src/styles/pages/partials/_article-wp-blocks.scss @@ -0,0 +1,168 @@ +@use "@styles/abstracts/functions" as fun; +@use "@styles/abstracts/mixins" as mix; +@use "@styles/abstracts/placeholders"; + +@mixin styles { +  .wp-block-quote { +    margin: var(--spacing-sm) 0; +    padding: var(--spacing-sm); +    position: relative; +    border: fun.convert-px(1) solid var(--color-primary-lighter); +    border-left: fun.convert-px(5) solid var(--color-primary-lighter); +    box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow), +      fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0 +        var(--color-shadow-light), +      fun.convert-px(3) fun.convert-px(3) fun.convert-px(6) 0 +        var(--color-shadow-light); +    font-style: italic; + +    > *:last-child { +      margin: 0; +    } + +    cite { +      font-size: var(--font-size-sm); +      font-style: normal; +      font-weight: 600; +    } +  } + +  .wp-block-code, +  .wp-block-preformatted { +    margin: 0 auto var(--spacing-md); +    padding: var(--spacing-xs) var(--spacing-sm); +    background: var(--color-bg-secondary); +    border: fun.convert-px(1) solid var(--color-border-light); +    color: var(--color-fg); +  } + +  .wp-block-columns { +    display: grid; +    grid-template-columns: minmax(0, 1fr); +    gap: var(--spacing-md); +    margin: var(--spacing-md) 0; + +    @include mix.media("screen") { +      @include mix.dimensions("sm") { +        grid-template-columns: repeat(2, minmax(0, 1fr)); +      } +    } + +    &.are-vertically-aligned-center { +      align-items: center; +    } +  } + +  .wp-block-column { +    > *:first-child { +      margin-top: 0; +    } + +    > *:last-child { +      margin-bottom: 0; +    } +  } + +  .wp-block-gallery { +    display: grid; +    grid-template-columns: minmax(0, 1fr); +    gap: var(--spacing-sm); + +    .blocks-gallery-grid { +      @extend %reset-list; + +      grid-column: 1 / -1; +      grid-row: 1 / -1; +      display: grid; +      grid-template-columns: minmax(0, 1fr); +      gap: var(--spacing-sm); +    } + +    .blocks-gallery-item { +      figure { +        margin: 0; +      } + +      a { +        display: block; +        box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow), +          fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0 +            var(--color-shadow-light), +          fun.convert-px(3) fun.convert-px(3) fun.convert-px(6) 0 +            var(--color-shadow-light); + +        &:hover, +        &:focus { +          transform: scale(1.05); +          box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow), +            fun.convert-px(3) fun.convert-px(3) fun.convert-px(2) 0 +              var(--color-shadow-light), +            fun.convert-px(5) fun.convert-px(5) fun.convert-px(8) 0 +              var(--color-shadow-light); +        } + +        &:focus { +          outline: solid var(--color-primary-light); +        } + +        &:active { +          transform: scale(0.95); +          box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow), +            fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0 +              var(--color-shadow-light), +            0 0 0 0 var(--color-shadow-light); +          outline: none; +        } +      } +    } + +    &.aligncenter { +      .blocks-gallery-grid { +        align-items: center; +      } +    } + +    @for $i from 0 to 6 { +      &.columns-#{$i} { +        @include mix.media("screen") { +          @include mix.dimensions("xs") { +            grid-template-columns: repeat(2, minmax(0, 1fr)); + +            .blocks-gallery-grid { +              grid-template-columns: repeat(2, minmax(0, 1fr)); +            } +          } + +          @include mix.dimensions("sm") { +            grid-template-columns: repeat(#{$i}, minmax(0, 1fr)); + +            .blocks-gallery-grid { +              grid-template-columns: repeat(3, minmax(0, 1fr)); +            } +          } +        } +      } +    } +  } + +  .wp-block-image { +    img { +      display: block; +      margin: auto; +      box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow), +        fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0 +          var(--color-shadow-light), +        fun.convert-px(3) fun.convert-px(3) fun.convert-px(6) 0 +          var(--color-shadow-light); +      text-align: center; +    } +  } + +  .wp-block-video { +    box-shadow: 0 0 fun.convert-px(1) 0 var(--color-shadow), +      fun.convert-px(2) fun.convert-px(2) fun.convert-px(2) 0 +        var(--color-shadow-light), +      fun.convert-px(3) fun.convert-px(3) fun.convert-px(6) 0 +        var(--color-shadow-light); +  } +} diff --git a/src/styles/pages/topic.module.scss b/src/styles/pages/topic.module.scss new file mode 100644 index 0000000..badb694 --- /dev/null +++ b/src/styles/pages/topic.module.scss @@ -0,0 +1,6 @@ +@use "@styles/abstracts/functions" as fun; + +.logo { +  max-width: fun.convert-px(50); +  margin-right: var(--spacing-xs); +} diff --git a/src/ts/types/app.ts b/src/ts/types/app.ts index f354118..a3b9889 100644 --- a/src/ts/types/app.ts +++ b/src/ts/types/app.ts @@ -81,6 +81,7 @@ export type Page<T extends PageKind> = {  export type PageLink = {    id: number; +  logo?: Image;    name: string;    slug: string;  }; diff --git a/src/ts/types/raw-data.ts b/src/ts/types/raw-data.ts index 7e12e7f..ba6d596 100644 --- a/src/ts/types/raw-data.ts +++ b/src/ts/types/raw-data.ts @@ -89,14 +89,17 @@ export type RawThematic = RawPage & {  export type RawThematicPreview = Pick<    RawThematic, -  'databaseId' | 'slug' | 'title' +  'databaseId' | 'featuredImage' | 'slug' | 'title'  >;  export type RawTopic = RawPage & {    acfTopics: ACFTopics;  }; -export type RawTopicPreview = Pick<RawTopic, 'databaseId' | 'slug' | 'title'>; +export type RawTopicPreview = Pick< +  RawTopic, +  'databaseId' | 'featuredImage' | 'slug' | 'title' +>;  export type TotalItems = {    pageInfo: Pick<PageInfo, 'total'>; diff --git a/src/utils/helpers/pages.ts b/src/utils/helpers/pages.ts index 93582f0..62337db 100644 --- a/src/utils/helpers/pages.ts +++ b/src/utils/helpers/pages.ts @@ -5,12 +5,14 @@ import {    type RawThematicPreview,    type RawTopicPreview,  } from '@ts/types/raw-data'; +import { getImageFromRawData } from './images';  /**   * Convert raw data to a Link object.   *   * @param data - An object.   * @param {number} data.databaseId - The data id. + * @param {number} [data.logo] - The data logo.   * @param {string} data.slug - The data slug.   * @param {string} data.title - The data name.   * @returns {PageLink} The link data (id, slug and title). @@ -18,10 +20,11 @@ import {  export const getPageLinkFromRawData = (    data: RawThematicPreview | RawTopicPreview  ): PageLink => { -  const { databaseId, slug, title } = data; +  const { databaseId, featuredImage, slug, title } = data;    return {      id: databaseId, +    logo: featuredImage ? getImageFromRawData(featuredImage?.node) : undefined,      name: title,      slug,    }; diff --git a/src/utils/hooks/use-add-prism-class-attr.tsx b/src/utils/hooks/use-add-prism-class-attr.tsx new file mode 100644 index 0000000..7d33cc2 --- /dev/null +++ b/src/utils/hooks/use-add-prism-class-attr.tsx @@ -0,0 +1,60 @@ +import { useCallback, useEffect, useState } from 'react'; + +export type AttributesMap = { +  [key: string]: string; +}; + +export type useAddPrismClassAttrProps = { +  attributes?: AttributesMap; +  classNames?: string; +}; + +/** + * Add classnames and/or attributes to pre elements. + * + * @param props - An object of attributes and classnames. + */ +const useAddPrismClassAttr = ({ +  attributes, +  classNames, +}: useAddPrismClassAttrProps) => { +  const [elements, setElements] = useState<HTMLPreElement[]>([]); + +  useEffect(() => { +    const targetElements = document.querySelectorAll('pre'); +    setElements(Array.from(targetElements)); +  }, []); + +  const setClassNameAndAttributes = useCallback( +    (array: HTMLElement[]) => { +      array.forEach((el) => { +        if (classNames) { +          const classNamesArray = classNames.split(' '); +          const isCommandLine = el.classList.contains('command-line'); +          const removedClassName = isCommandLine +            ? 'line-numbers' +            : 'command-line'; +          const filteredClassNames = classNamesArray.filter( +            (className) => className !== removedClassName +          ); +          filteredClassNames.forEach((className) => +            el.classList.add(className) +          ); +        } + +        if (attributes) { +          for (const [key, value] of Object.entries(attributes)) { +            el.setAttribute(key, value); +          } +        } +      }); +    }, +    [attributes, classNames] +  ); + +  useEffect(() => { +    if (elements.length > 0) setClassNameAndAttributes(elements); +  }, [elements, setClassNameAndAttributes]); +}; + +export default useAddPrismClassAttr; | 
