diff options
| author | Armand Philippot <git@armandphilippot.com> | 2022-01-15 22:45:57 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2022-01-15 22:51:30 +0100 |
| commit | aa1ca65e7c9807c6d6020e39166536297fe1cdae (patch) | |
| tree | 2648da350fec3b71ab7f575d63e4c63ba08248b1 /src | |
| parent | 16dbb4742264edac82fa6bb8e461259d097f4437 (diff) | |
chore: update sidebar and widgets styles
I'm now using a widget that can be expanded/collapsed. It also allows
me to handle more effectively widgets overflow and to avoid styles
repetitions.
However, with stylelint rule "no-descending-specificity", I'm not sure
if the stylesheets are really logical... Maybe I should deactivate this
rule.
Diffstat (limited to 'src')
41 files changed, 547 insertions, 165 deletions
diff --git a/src/components/PostHeader/PostHeader.module.scss b/src/components/PostHeader/PostHeader.module.scss index 3db92da..33dc488 100644 --- a/src/components/PostHeader/PostHeader.module.scss +++ b/src/components/PostHeader/PostHeader.module.scss @@ -4,10 +4,13 @@ .wrapper { composes: grid from "@styles/layout/_grid.scss"; max-width: 100%; - margin-bottom: var(--spacing-md); position: relative; @include mix.media("screen") { + @include mix.dimensions("md") { + margin-bottom: var(--spacing-md); + } + @include mix.dimensions("lg") { --grid-gap: var(--spacing-lg); } diff --git a/src/components/PostPreview/PostPreview.module.scss b/src/components/PostPreview/PostPreview.module.scss index 8039a87..8c14000 100644 --- a/src/components/PostPreview/PostPreview.module.scss +++ b/src/components/PostPreview/PostPreview.module.scss @@ -48,7 +48,7 @@ h2.title { .wrapper { margin: 0; padding: var(--spacing-sm) var(--spacing-sm) var(--spacing-md); - border: fun.convert-px(1) solid var(--color-border-light); + border: fun.convert-px(1) solid var(--color-primary-dark); border-radius: fun.convert-px(3); box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) 0 var(--color-shadow-light), diff --git a/src/components/PostPreview/PostPreview.tsx b/src/components/PostPreview/PostPreview.tsx index 3ea4c40..3bf7bdb 100644 --- a/src/components/PostPreview/PostPreview.tsx +++ b/src/components/PostPreview/PostPreview.tsx @@ -6,8 +6,7 @@ import styles from './PostPreview.module.scss'; import Image from 'next/image'; import { ButtonLink } from '@components/Buttons'; import { ArrowIcon } from '@components/Icons'; - -type TitleLevel = 2 | 3 | 4 | 5 | 6; +import { TitleLevel } from '@ts/types/app'; const PostPreview = ({ post, diff --git a/src/components/PostsList/PostsList.module.scss b/src/components/PostsList/PostsList.module.scss index 6c3f93e..5d3ee95 100644 --- a/src/components/PostsList/PostsList.module.scss +++ b/src/components/PostsList/PostsList.module.scss @@ -19,7 +19,7 @@ grid-column: 1; justify-self: end; position: sticky; - top: 0; + top: var(--spacing-xs); margin-right: var(--spacing-lg); } } diff --git a/src/components/Sidebar/Sidebar.module.scss b/src/components/Sidebar/Sidebar.module.scss index 83c1024..18291b6 100644 --- a/src/components/Sidebar/Sidebar.module.scss +++ b/src/components/Sidebar/Sidebar.module.scss @@ -2,37 +2,44 @@ .wrapper { grid-column: 2; - margin-top: var(--spacing-lg); + margin: var(--spacing-md) 0; @include mix.media("screen") { @include mix.dimensions("md") { - grid-column: 3; - grid-row: 2; align-self: stretch; display: flex; - flex-flow: column nowrap; - margin-top: 0; - position: relative; - visibility: hidden; + flex-flow: column; + justify-content: flex-start; + margin: var(--spacing-xs); - &:hover { - visibility: visible; + &--right { + grid-row: 2 / 4; + grid-column: 3; } + } - > * { - visibility: visible; + @include mix.dimensions("lg") { + &--left { + grid-row: 2 / 4; + grid-column: 1; } } } } .body { + display: flex; + flex-flow: column; + justify-content: flex-start; + max-height: 100vh; + @include mix.media("screen") { @include mix.dimensions("md") { align-self: flex-start; width: 100%; + max-height: calc(100vh - (var(--spacing-xs) * 2)); position: sticky; - top: 0; + top: var(--spacing-xs); } } } diff --git a/src/components/Sidebar/Sidebar.tsx b/src/components/Sidebar/Sidebar.tsx index 8c9fa1d..f319f9e 100644 --- a/src/components/Sidebar/Sidebar.tsx +++ b/src/components/Sidebar/Sidebar.tsx @@ -1,10 +1,32 @@ -import { FunctionComponent } from 'react'; +import { Children, cloneElement, isValidElement, ReactNode } from 'react'; import styles from './Sidebar.module.scss'; -const Sidebar: FunctionComponent = ({ children }) => { +type SidebarPosition = 'left' | 'right'; + +const Sidebar = ({ + children, + position, + title, +}: { + children: ReactNode; + position: SidebarPosition; + title?: string; +}) => { + const childrenWithProps = Children.map(children, (child) => { + if (isValidElement(child)) { + return cloneElement(child, { titleLevel: title ? 3 : 2 }); + } + return child; + }); + + const positionClass = `wrapper--${position}`; + return ( - <aside className={styles.wrapper}> - <div className={styles.body}>{children}</div> + <aside className={`${styles.wrapper} ${styles[positionClass]}`}> + <div className={styles.body}> + {title && <h2>{title}</h2>} + {childrenWithProps} + </div> </aside> ); }; diff --git a/src/components/ToC/ToC.module.scss b/src/components/ToC/ToC.module.scss deleted file mode 100644 index 0f08b87..0000000 --- a/src/components/ToC/ToC.module.scss +++ /dev/null @@ -1,27 +0,0 @@ -@use "@styles/abstracts/mixins" as mix; - -.wrapper { - padding-bottom: var(--spacing-sm); - - @include mix.media("screen") { - @include mix.dimensions("lg") { - max-height: 100vh; - position: sticky; - top: 0; - overflow: auto; - visibility: hidden; - - > * { - visibility: visible; - } - - &:hover { - visibility: visible; - } - } - } -} - -.list { - margin-bottom: 0; -} diff --git a/src/components/Widget/Widget.module.scss b/src/components/Widget/Widget.module.scss deleted file mode 100644 index 5f37c7a..0000000 --- a/src/components/Widget/Widget.module.scss +++ /dev/null @@ -1,7 +0,0 @@ -.title { - margin: 0; -} - -.list { - margin: var(--spacing-sm) 0; -} diff --git a/src/components/WidgetParts/ExpandableWidget/ExpandableWidget.module.scss b/src/components/WidgetParts/ExpandableWidget/ExpandableWidget.module.scss new file mode 100644 index 0000000..058d805 --- /dev/null +++ b/src/components/WidgetParts/ExpandableWidget/ExpandableWidget.module.scss @@ -0,0 +1,146 @@ +@use "@styles/abstracts/functions" as fun; +@use "@styles/abstracts/mixins" as mix; + +.title { + margin: 0; + padding: 0; + background: none; + font-size: var(--font-size-xl); + text-align: left; +} + +.icon { + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: center; + width: fun.convert-px(30); + height: fun.convert-px(30); + background: var(--color-bg); + border: fun.convert-px(1) solid var(--color-primary); + border-radius: fun.convert-px(3); + color: var(--color-primary); + font-weight: 800; + transition: all 0.25s ease-in-out 0s; + + &::before, + &::after { + content: ""; + background: var(--color-primary); + transition: all 0.4s ease-out 0s; + } + + &::before { + width: 10%; + height: 60%; + position: relative; + left: 30%; + } + + &::after { + width: 60%; + height: 10%; + position: relative; + left: -5%; + } +} + +.body { + width: 100%; + margin: 0; + overflow-y: auto; + visibility: hidden; + opacity: 0; + height: 0; + transition: all 0.5s ease-in-out 0s; + + &--borders { + border: fun.convert-px(2) solid var(--color-primary-dark); + } + + > *:last-child { + margin-bottom: 0; + } + + @include mix.media("screen") { + @include mix.dimensions("md") { + font-size: var(--font-size-sm); + font-weight: 500; + } + } +} + +.header { + flex: 0 0 auto; + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: space-between; + gap: var(--spacing-md); + width: 100%; + min-height: var(--header-height); + position: sticky; + top: 0; + background: var(--color-bg); + border: none; + border-bottom: fun.convert-px(2) solid var(--color-primary-dark); + cursor: pointer; + transition: background 0.2s ease-in-out 0s; + + > button { + padding: 0 var(--spacing-xs); + } +} + +.wrapper--expanded .icon { + &::before { + height: 0; + } +} + +.header:hover, +.header:focus { + .icon { + background: var(--color-primary-light); + color: var(--color-fg-inverted); + transform: scale(1.2); + + &::before, + &::after { + background: var(--color-bg); + } + } +} + +.wrapper { + --header-height: #{fun.convert-px(65)}; + + display: flex; + flex-flow: column; + min-height: var(--header-height); + position: relative; + overflow-y: hidden; + + &:first-of-type { + .header { + border-top: fun.convert-px(2) solid var(--color-primary-dark); + } + } +} + +.wrapper--expanded { + ~ .wrapper { + .header { + border-top: fun.convert-px(2) solid var(--color-primary-dark); + } + } + + .body { + visibility: visible; + opacity: 1; + min-height: inherit; + height: 100%; + margin: var(--spacing-sm) 0; + transition: all 0.45s ease-in-out 0s; + } +} diff --git a/src/components/WidgetParts/ExpandableWidget/ExpandableWidget.tsx b/src/components/WidgetParts/ExpandableWidget/ExpandableWidget.tsx new file mode 100644 index 0000000..52b5c06 --- /dev/null +++ b/src/components/WidgetParts/ExpandableWidget/ExpandableWidget.tsx @@ -0,0 +1,47 @@ +import { t } from '@lingui/macro'; +import { TitleLevel } from '@ts/types/app'; +import { ReactNode, useState } from 'react'; +import styles from './ExpandableWidget.module.scss'; + +const ExpandableWidget = ({ + children, + title, + titleLevel = 2, + expand = false, + withBorders = false, +}: { + children: ReactNode; + title: string; + titleLevel?: TitleLevel; + expand?: boolean; + withBorders?: boolean; +}) => { + const [isExpanded, setIsExpanded] = useState<boolean>(expand); + + const handleExpanse = () => setIsExpanded((prev) => !prev); + + const TitleTag = `h${titleLevel}` as keyof JSX.IntrinsicElements; + + const wrapperClasses = `${styles.wrapper} ${ + isExpanded ? styles['wrapper--expanded'] : '' + }`; + + const bodyClasses = `${styles.body} ${ + withBorders ? styles['body--borders'] : '' + }`; + + return ( + <div className={wrapperClasses}> + <button type="button" className={styles.header} onClick={handleExpanse}> + <span className="screen-reader-text"> + {isExpanded ? t`Collapse` : t`Expand`} + </span> + <TitleTag className={styles.title}>{title}</TitleTag> + <span className={styles.icon} aria-hidden={true}></span> + </button> + <div className={bodyClasses}>{children}</div> + </div> + ); +}; + +export default ExpandableWidget; diff --git a/src/components/WidgetParts/List/List.module.scss b/src/components/WidgetParts/List/List.module.scss new file mode 100644 index 0000000..5da5741 --- /dev/null +++ b/src/components/WidgetParts/List/List.module.scss @@ -0,0 +1,47 @@ +@use "@styles/abstracts/functions" as fun; + +.list { + margin: 0; + padding: 0; + list-style-type: none; + + .list { + border: none; + border-top: fun.convert-px(1) solid var(--color-primary-dark); + } + + li { + margin: 0; + + &:not(:last-of-type) { + border-bottom: fun.convert-px(1) solid var(--color-primary-dark); + } + } + + a { + display: flex; + flex-flow: row nowrap; + width: 100%; + padding: var(--spacing-2xs) var(--spacing-xs); + background: none; + text-decoration: underline solid transparent 0; + transition: all 0.16s ease-in-out 0s, text-decoration-color 0s; + + &:hover, + &:focus { + background: var(--color-bg-secondary); + } + + &:focus { + color: var(--color-primary); + text-decoration-color: var(--color-primary-light); + text-decoration-thickness: 0.25ex; + } + + &:active { + background: var(--color-bg-tertiary); + text-decoration-color: transparent; + text-decoration-thickness: 0; + } + } +} diff --git a/src/components/WidgetParts/List/List.tsx b/src/components/WidgetParts/List/List.tsx new file mode 100644 index 0000000..317c4d1 --- /dev/null +++ b/src/components/WidgetParts/List/List.tsx @@ -0,0 +1,7 @@ +import styles from './List.module.scss'; + +const List = ({ items }: { items: Array<any> }) => { + return <ul className={styles.list}>{items}</ul>; +}; + +export default List; diff --git a/src/components/WidgetParts/OrderedList/OrderedList.module.scss b/src/components/WidgetParts/OrderedList/OrderedList.module.scss new file mode 100644 index 0000000..a286932 --- /dev/null +++ b/src/components/WidgetParts/OrderedList/OrderedList.module.scss @@ -0,0 +1,66 @@ +@use "@styles/abstracts/functions" as fun; +@use "@styles/abstracts/placeholders"; + +.list { + @extend %reset-ordered-list; + counter-reset: link; + + .list { + border-top: fun.convert-px(1) solid var(--color-primary-dark); + } + + a { + display: flex; + flex-flow: row nowrap; + width: 100%; + padding: var(--spacing-2xs) var(--spacing-xs); + background: none; + text-decoration: underline solid transparent 0; + transition: all 0.16s ease-in-out 0s, text-decoration-color 0s; + counter-increment: link; + + &:hover, + &:focus { + background: var(--color-bg-secondary); + } + + &:focus { + color: var(--color-primary); + text-decoration-color: var(--color-primary-light); + text-decoration-thickness: 0.25ex; + } + + &:active { + background: var(--color-bg-tertiary); + text-decoration-color: transparent; + text-decoration-thickness: 0; + } + + &::before { + content: counters(link, ".") ". "; + color: var(--color-secondary); + padding-right: var(--spacing-2xs); + } + } + + li { + width: 100%; + margin: 0; + + &:not(:last-of-type) { + border-bottom: fun.convert-px(1) solid var(--color-primary-dark); + } + + &::before { + display: none; + } + } + + li li a::before { + padding-left: var(--spacing-sm); + } + + li li li a::before { + padding-left: var(--spacing-lg); + } +} diff --git a/src/components/WidgetParts/OrderedList/OrderedList.tsx b/src/components/WidgetParts/OrderedList/OrderedList.tsx new file mode 100644 index 0000000..a12ec06 --- /dev/null +++ b/src/components/WidgetParts/OrderedList/OrderedList.tsx @@ -0,0 +1,7 @@ +import styles from './OrderedList.module.scss'; + +const OrderedList = ({ items }: { items: Array<any> }) => { + return <ol className={styles.list}>{items}</ol>; +}; + +export default OrderedList; diff --git a/src/components/WidgetParts/index.tsx b/src/components/WidgetParts/index.tsx new file mode 100644 index 0000000..59df3bd --- /dev/null +++ b/src/components/WidgetParts/index.tsx @@ -0,0 +1,5 @@ +import ExpandableWidget from './ExpandableWidget/ExpandableWidget'; +import List from './List/List'; +import OrderedList from './OrderedList/OrderedList'; + +export { ExpandableWidget, List, OrderedList }; diff --git a/src/components/Widget/CVPreview/CVPreview.module.scss b/src/components/Widgets/CVPreview/CVPreview.module.scss index 6ddd696..6ddd696 100644 --- a/src/components/Widget/CVPreview/CVPreview.module.scss +++ b/src/components/Widgets/CVPreview/CVPreview.module.scss diff --git a/src/components/Widget/CVPreview/CVPreview.tsx b/src/components/Widgets/CVPreview/CVPreview.tsx index f0bd0fe..e52a9b2 100644 --- a/src/components/Widget/CVPreview/CVPreview.tsx +++ b/src/components/Widgets/CVPreview/CVPreview.tsx @@ -1,3 +1,4 @@ +import { ExpandableWidget } from '@components/WidgetParts'; import { Trans } from '@lingui/macro'; import Image from 'next/image'; import Link from 'next/link'; @@ -13,8 +14,7 @@ const CVPreview = ({ pdf: string; }) => { return ( - <div> - <h2>{title}</h2> + <ExpandableWidget title={title} expand={true}> <div className={styles.preview}> <Image src={imgSrc} @@ -29,7 +29,7 @@ const CVPreview = ({ Download <Link href={pdf}>CV in PDF</Link> </Trans> </p> - </div> + </ExpandableWidget> ); }; diff --git a/src/components/Widget/RecentPosts/RecentPosts.module.scss b/src/components/Widgets/RecentPosts/RecentPosts.module.scss index 95ad7fe..95ad7fe 100644 --- a/src/components/Widget/RecentPosts/RecentPosts.module.scss +++ b/src/components/Widgets/RecentPosts/RecentPosts.module.scss diff --git a/src/components/Widget/RecentPosts/RecentPosts.tsx b/src/components/Widgets/RecentPosts/RecentPosts.tsx index 1569284..1569284 100644 --- a/src/components/Widget/RecentPosts/RecentPosts.tsx +++ b/src/components/Widgets/RecentPosts/RecentPosts.tsx diff --git a/src/components/Widget/RelatedThematics/RelatedThematics.tsx b/src/components/Widgets/RelatedThematics/RelatedThematics.tsx index 24d60e2..afe3460 100644 --- a/src/components/Widget/RelatedThematics/RelatedThematics.tsx +++ b/src/components/Widgets/RelatedThematics/RelatedThematics.tsx @@ -1,7 +1,7 @@ +import { ExpandableWidget, List } from '@components/WidgetParts'; import { t } from '@lingui/macro'; import { ThematicPreview } from '@ts/types/taxonomies'; import Link from 'next/link'; -import styles from '../Widget.module.scss'; const RelatedThematics = ({ thematics }: { thematics: ThematicPreview[] }) => { const sortedThematics = [...thematics].sort((a, b) => @@ -19,12 +19,12 @@ const RelatedThematics = ({ thematics }: { thematics: ThematicPreview[] }) => { }); return ( - <div> - <h2 className={styles.title}> - {thematics.length > 1 ? t`Related thematics` : t`Related thematic`} - </h2> - <ul className={styles.list}>{thematicsList}</ul> - </div> + <ExpandableWidget + title={thematics.length > 1 ? t`Related thematics` : t`Related thematic`} + withBorders={true} + > + <List items={thematicsList} /> + </ExpandableWidget> ); }; diff --git a/src/components/Widget/RelatedTopics/RelatedTopics.tsx b/src/components/Widgets/RelatedTopics/RelatedTopics.tsx index 422c06f..aab8cc1 100644 --- a/src/components/Widget/RelatedTopics/RelatedTopics.tsx +++ b/src/components/Widgets/RelatedTopics/RelatedTopics.tsx @@ -1,7 +1,7 @@ +import { ExpandableWidget, List } from '@components/WidgetParts'; import { t } from '@lingui/macro'; import { SubjectPreview } from '@ts/types/taxonomies'; import Link from 'next/link'; -import styles from '../Widget.module.scss'; const RelatedTopics = ({ topics }: { topics: SubjectPreview[] }) => { const sortedSubjects = [...topics].sort((a, b) => @@ -19,12 +19,12 @@ const RelatedTopics = ({ topics }: { topics: SubjectPreview[] }) => { }); return ( - <div> - <h2 className={styles.title}> - {topics.length > 1 ? t`Related topics` : t`Related topic`} - </h2> - <ul className={styles.list}>{subjects}</ul> - </div> + <ExpandableWidget + title={topics.length > 1 ? t`Related topics` : t`Related topic`} + withBorders={true} + > + <List items={subjects} /> + </ExpandableWidget> ); }; diff --git a/src/components/Widget/Sharing/Sharing.module.scss b/src/components/Widgets/Sharing/Sharing.module.scss index 7ecb5ff..3477c88 100644 --- a/src/components/Widget/Sharing/Sharing.module.scss +++ b/src/components/Widgets/Sharing/Sharing.module.scss @@ -2,22 +2,20 @@ @use "@styles/abstracts/mixins" as mix; @use "@styles/abstracts/placeholders"; -.wrapper { - padding-bottom: var(--spacing-sm); +.list { + @extend %flex-list; + + gap: var(--spacing-sm); + padding: var(--spacing-2xs) 0 0 var(--spacing-2xs); @include mix.media("screen") { @include mix.dimensions("md") { + gap: var(--spacing-xs); width: min-content; } } } -.list { - @extend %flex-list; - - gap: var(--spacing-sm); -} - .link { display: flex; flex-flow: row nowrap; diff --git a/src/components/Widget/Sharing/Sharing.tsx b/src/components/Widgets/Sharing/Sharing.tsx index 4df8e0d..bc52f9b 100644 --- a/src/components/Widget/Sharing/Sharing.tsx +++ b/src/components/Widgets/Sharing/Sharing.tsx @@ -1,3 +1,4 @@ +import { ExpandableWidget } from '@components/WidgetParts'; import sharingMedia from '@config/sharing'; import { t } from '@lingui/macro'; import { useRouter } from 'next/router'; @@ -101,10 +102,11 @@ const Sharing = ({ excerpt, title }: { excerpt: string; title: string }) => { }; return ( - <div className={styles.wrapper}> - <h2>{t`Share`}</h2> - <ul className={styles.list}>{getItems()}</ul> - </div> + <ExpandableWidget title={t`Share`} expand={true}> + <ul className={`${styles.list} ${styles['list--sharing']}`}> + {getItems()} + </ul> + </ExpandableWidget> ); }; diff --git a/src/components/Widget/SocialMedia/SocialMedia.module.scss b/src/components/Widgets/SocialMedia/SocialMedia.module.scss index 70ef5d9..c8ad759 100644 --- a/src/components/Widget/SocialMedia/SocialMedia.module.scss +++ b/src/components/Widgets/SocialMedia/SocialMedia.module.scss @@ -6,6 +6,7 @@ gap: var(--spacing-xs); align-items: center; + padding: var(--spacing-2xs) 0 0 var(--spacing-2xs); } .link { diff --git a/src/components/Widget/SocialMedia/SocialMedia.tsx b/src/components/Widgets/SocialMedia/SocialMedia.tsx index 9ca0627..351fd48 100644 --- a/src/components/Widget/SocialMedia/SocialMedia.tsx +++ b/src/components/Widgets/SocialMedia/SocialMedia.tsx @@ -4,6 +4,7 @@ import GitlabIcon from '@assets/images/social-media/gitlab.svg'; import LinkedInIcon from '@assets/images/social-media/linkedin.svg'; import TwitterIcon from '@assets/images/social-media/twitter.svg'; import styles from './SocialMedia.module.scss'; +import { ExpandableWidget } from '@components/WidgetParts'; const SocialMedia = ({ title, @@ -62,10 +63,9 @@ const SocialMedia = ({ }); return ( - <div> - <h2>{title}</h2> + <ExpandableWidget title={title} expand={true}> <ul className={styles.list}>{items}</ul> - </div> + </ExpandableWidget> ); }; diff --git a/src/components/Widget/ThematicsList/ThematicsList.tsx b/src/components/Widgets/ThematicsList/ThematicsList.tsx index d17dbd1..8523bd1 100644 --- a/src/components/Widget/ThematicsList/ThematicsList.tsx +++ b/src/components/Widgets/ThematicsList/ThematicsList.tsx @@ -1,11 +1,18 @@ +import { ExpandableWidget, List } from '@components/WidgetParts'; import { t } from '@lingui/macro'; import { getAllThematics } from '@services/graphql/queries'; +import { TitleLevel } from '@ts/types/app'; import Link from 'next/link'; import { useRouter } from 'next/router'; import useSWR from 'swr'; -import styles from '../Widget.module.scss'; -const ThematicsList = ({ title }: { title: string }) => { +const ThematicsList = ({ + title, + titleLevel, +}: { + title: string; + titleLevel?: TitleLevel; +}) => { const router = useRouter(); const isThematic = () => router.asPath.includes('/thematique/'); const currentThematicSlug = isThematic() @@ -30,10 +37,9 @@ const ThematicsList = ({ title }: { title: string }) => { }); return ( - <div> - <h2 className={styles.title}>{title}</h2> - <ul className={styles.list}>{thematics}</ul> - </div> + <ExpandableWidget title={title} titleLevel={titleLevel} withBorders={true}> + <List items={thematics} /> + </ExpandableWidget> ); }; diff --git a/src/components/Widgets/ToC/ToC.module.scss b/src/components/Widgets/ToC/ToC.module.scss new file mode 100644 index 0000000..a296659 --- /dev/null +++ b/src/components/Widgets/ToC/ToC.module.scss @@ -0,0 +1,70 @@ +@use "@styles/abstracts/functions" as fun; +@use "@styles/abstracts/mixins" as mix; + +.item { + width: 100%; + margin: 0; + + &::before { + display: none; + } +} + +.link { + display: flex; + flex-flow: row nowrap; + width: 100%; + padding: var(--spacing-2xs) var(--spacing-sm); + background: none; + border-bottom: fun.convert-px(1) solid var(--color-border-lighter); + text-decoration: underline solid transparent 0; + transition: all 0.16s ease-in-out 0s, text-decoration-color 0s; + + &:hover, + &:focus { + background: var(--color-bg-secondary); + } + + &:focus { + color: var(--color-primary); + text-decoration-color: var(--color-primary-light); + text-decoration-thickness: 0.25ex; + } + + &:active { + background: var(--color-bg-tertiary); + text-decoration-color: transparent; + text-decoration-thickness: 0; + } +} + +.list { + width: 100%; + margin: 0; + counter-reset: link; + + @include mix.media("screen") { + @include mix.dimensions("lg") { + font-size: var(--font-size-sm); + font-weight: 500; + } + } + + > .item .link { + counter-increment: link; + + &::before { + content: counters(link, ".") ". "; + color: var(--color-secondary); + padding-right: var(--spacing-2xs); + } + } + + .item .item .link::before { + padding-left: var(--spacing-sm); + } + + .item .item .item .link::before { + padding-left: var(--spacing-lg); + } +} diff --git a/src/components/ToC/ToC.tsx b/src/components/Widgets/ToC/ToC.tsx index b172b3a..6010354 100644 --- a/src/components/ToC/ToC.tsx +++ b/src/components/Widgets/ToC/ToC.tsx @@ -1,7 +1,7 @@ +import { ExpandableWidget, OrderedList } from '@components/WidgetParts'; import { t } from '@lingui/macro'; import { Heading } from '@ts/types/app'; import useHeadingsTree from '@utils/hooks/useHeadingsTree'; -import styles from './ToC.module.scss'; const ToC = () => { const headingsTree = useHeadingsTree('article'); @@ -12,17 +12,18 @@ const ToC = () => { return ( <li key={heading.id}> <a href={`#${heading.id}`}>{heading.title}</a> - {heading.children.length > 0 && <ol>{getItems(heading.children)}</ol>} + {heading.children.length > 0 && ( + <OrderedList items={getItems(heading.children)} /> + )} </li> ); }); }; return ( - <div className={styles.wrapper}> - <h2>{title}</h2> - <ol className={styles.list}>{getItems(headingsTree)}</ol> - </div> + <ExpandableWidget title={title} expand={true} withBorders={true}> + <OrderedList items={getItems(headingsTree)} /> + </ExpandableWidget> ); }; diff --git a/src/components/Widget/TopicsList/TopicsList.tsx b/src/components/Widgets/TopicsList/TopicsList.tsx index 50205d7..5bf12b9 100644 --- a/src/components/Widget/TopicsList/TopicsList.tsx +++ b/src/components/Widgets/TopicsList/TopicsList.tsx @@ -1,11 +1,18 @@ +import { ExpandableWidget, List } from '@components/WidgetParts'; import { t } from '@lingui/macro'; import { getAllSubjects } from '@services/graphql/queries'; +import { TitleLevel } from '@ts/types/app'; import Link from 'next/link'; import { useRouter } from 'next/router'; import useSWR from 'swr'; -import styles from '../Widget.module.scss'; -const TopicsList = ({ title }: { title: string }) => { +const TopicsList = ({ + title, + titleLevel, +}: { + title: string; + titleLevel?: TitleLevel; +}) => { const router = useRouter(); const isTopic = () => router.asPath.includes('/sujet/'); const currentTopicSlug = isTopic() @@ -30,10 +37,9 @@ const TopicsList = ({ title }: { title: string }) => { }); return ( - <div> - <h2 className={styles.title}>{title}</h2> - <ul className={styles.list}>{subjects}</ul> - </div> + <ExpandableWidget title={title} titleLevel={titleLevel} withBorders={true}> + <List items={subjects} /> + </ExpandableWidget> ); }; diff --git a/src/components/Widget/index.tsx b/src/components/Widgets/index.tsx index 08e3a5a..8354449 100644 --- a/src/components/Widget/index.tsx +++ b/src/components/Widgets/index.tsx @@ -5,6 +5,7 @@ import RelatedTopics from './RelatedTopics/RelatedTopics'; import Sharing from './Sharing/Sharing'; import SocialMedia from './SocialMedia/SocialMedia'; import ThematicsList from './ThematicsList/ThematicsList'; +import ToC from './ToC/ToC'; import TopicsList from './TopicsList/TopicsList'; export { @@ -15,5 +16,6 @@ export { Sharing, SocialMedia, ThematicsList, + ToC, TopicsList, }; diff --git a/src/content b/src/content -Subproject f399fbcb373436ab05f3e01ce762ce1680e61f7 +Subproject c6f91781084aaf0b7d6f9b2a5d177e21e6d0d96 diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx index fb79b41..509be4f 100644 --- a/src/pages/article/[slug].tsx +++ b/src/pages/article/[slug].tsx @@ -3,7 +3,6 @@ import CommentsList from '@components/CommentsList/CommentsList'; import { getLayout } from '@components/Layouts/Layout'; import PostFooter from '@components/PostFooter/PostFooter'; import PostHeader from '@components/PostHeader/PostHeader'; -import ToC from '@components/ToC/ToC'; import { config } from '@config/website'; import { getAllPostsSlug, getPostBySlug } from '@services/graphql/queries'; import { NextPageWithLayout } from '@ts/types/app'; @@ -17,7 +16,7 @@ import Prism from 'prismjs'; import { ParsedUrlQuery } from 'querystring'; import { useEffect } from 'react'; import styles from '@styles/pages/Page.module.scss'; -import { Sharing } from '@components/Widget'; +import { Sharing, ToC } from '@components/Widgets'; import Sidebar from '@components/Sidebar/Sidebar'; const SingleArticle: NextPageWithLayout<ArticleProps> = ({ post }) => { @@ -60,15 +59,15 @@ const SingleArticle: NextPageWithLayout<ArticleProps> = ({ post }) => { </Head> <article className={styles.article}> <PostHeader intro={intro} meta={meta} title={title} /> - <aside className={styles.toc}> + <Sidebar position="left"> <ToC /> - </aside> + </Sidebar> <div className={styles.body} dangerouslySetInnerHTML={{ __html: content }} ></div> <PostFooter subjects={subjects} /> - <Sidebar> + <Sidebar position="right"> <Sharing title={title} excerpt={intro} /> </Sidebar> <section className={styles.comments}> diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx index e0d35cd..b20b647 100644 --- a/src/pages/blog/index.tsx +++ b/src/pages/blog/index.tsx @@ -12,7 +12,7 @@ import useSWRInfinite from 'swr/infinite'; import { Button } from '@components/Buttons'; import { getPublishedPosts } from '@services/graphql/queries'; import PostHeader from '@components/PostHeader/PostHeader'; -import { ThematicsList, TopicsList } from '@components/Widget'; +import { ThematicsList, TopicsList } from '@components/Widgets'; import Sidebar from '@components/Sidebar/Sidebar'; import styles from '@styles/pages/Page.module.scss'; import { useRef } from 'react'; @@ -74,7 +74,7 @@ const Blog: NextPageWithLayout<BlogPageProps> = ({ fallback }) => { >{t`Load more?`}</Button> )} </div> - <Sidebar> + <Sidebar position="right" title={t`Filter by`}> <ThematicsList title={t`Thematics`} /> <TopicsList title={t`Topics`} /> </Sidebar> diff --git a/src/pages/contact.tsx b/src/pages/contact.tsx index 5e6d138..bafa5e9 100644 --- a/src/pages/contact.tsx +++ b/src/pages/contact.tsx @@ -11,7 +11,7 @@ import Head from 'next/head'; import { FormEvent, useState } from 'react'; import PostHeader from '@components/PostHeader/PostHeader'; import styles from '@styles/pages/Page.module.scss'; -import { SocialMedia } from '@components/Widget'; +import { SocialMedia } from '@components/Widgets'; import Sidebar from '@components/Sidebar/Sidebar'; const ContactPage: NextPageWithLayout = () => { @@ -113,7 +113,7 @@ const ContactPage: NextPageWithLayout = () => { </FormItem> </Form> </div> - <Sidebar> + <Sidebar position="right"> <SocialMedia title={t`Find me elsewhere`} github={true} diff --git a/src/pages/cv.tsx b/src/pages/cv.tsx index c771bb2..01eab4c 100644 --- a/src/pages/cv.tsx +++ b/src/pages/cv.tsx @@ -1,5 +1,4 @@ import { getLayout } from '@components/Layouts/Layout'; -import ToC from '@components/ToC/ToC'; import { seo } from '@config/seo'; import { NextPageWithLayout } from '@ts/types/app'; import { loadTranslation } from '@utils/helpers/i18n'; @@ -9,7 +8,7 @@ import CVContent, { intro, meta, pdf, image } from '@content/pages/cv.mdx'; import PostHeader from '@components/PostHeader/PostHeader'; import { ArticleMeta } from '@ts/types/articles'; import styles from '@styles/pages/Page.module.scss'; -import { CVPreview, SocialMedia } from '@components/Widget'; +import { CVPreview, SocialMedia, ToC } from '@components/Widgets'; import { t } from '@lingui/macro'; import Sidebar from '@components/Sidebar/Sidebar'; @@ -33,13 +32,13 @@ const CV: NextPageWithLayout = () => { className={`${styles.article} ${styles['article--no-comments']}`} > <PostHeader intro={intro} meta={pageMeta} title={meta.title} /> - <aside className={styles.toc}> + <Sidebar position="left"> <ToC /> - </aside> + </Sidebar> <div className={styles.body}> <CVContent /> </div> - <Sidebar> + <Sidebar position="right"> <CVPreview title={t`Other formats`} imgSrc={image} pdf={pdf} /> <SocialMedia title={t`Open-source projects`} diff --git a/src/pages/mentions-legales.tsx b/src/pages/mentions-legales.tsx index ea98e20..fcaef06 100644 --- a/src/pages/mentions-legales.tsx +++ b/src/pages/mentions-legales.tsx @@ -1,5 +1,4 @@ import { getLayout } from '@components/Layouts/Layout'; -import ToC from '@components/ToC/ToC'; import { seo } from '@config/seo'; import { NextPageWithLayout } from '@ts/types/app'; import { loadTranslation } from '@utils/helpers/i18n'; @@ -12,6 +11,8 @@ import LegalNoticeContent, { import PostHeader from '@components/PostHeader/PostHeader'; import { ArticleMeta } from '@ts/types/articles'; import styles from '@styles/pages/Page.module.scss'; +import { ToC } from '@components/Widgets'; +import Sidebar from '@components/Sidebar/Sidebar'; const LegalNotice: NextPageWithLayout = () => { const dates = { @@ -33,9 +34,9 @@ const LegalNotice: NextPageWithLayout = () => { className={`${styles.article} ${styles['article--no-comments']}`} > <PostHeader intro={intro} meta={pageMeta} title={meta.title} /> - <aside className={styles.toc}> + <Sidebar position="left"> <ToC /> - </aside> + </Sidebar> <div className={styles.body}> <LegalNoticeContent /> </div> diff --git a/src/pages/recherche/index.tsx b/src/pages/recherche/index.tsx index 57f40e2..c45f9f0 100644 --- a/src/pages/recherche/index.tsx +++ b/src/pages/recherche/index.tsx @@ -14,7 +14,7 @@ import { useRouter } from 'next/router'; import { useEffect, useRef, useState } from 'react'; import useSWRInfinite from 'swr/infinite'; import Sidebar from '@components/Sidebar/Sidebar'; -import { ThematicsList, TopicsList } from '@components/Widget'; +import { ThematicsList, TopicsList } from '@components/Widgets'; import styles from '@styles/pages/Page.module.scss'; const Search: NextPageWithLayout = () => { @@ -97,7 +97,7 @@ const Search: NextPageWithLayout = () => { >{t`Load more?`}</Button> )} </div> - <Sidebar> + <Sidebar position="right"> <ThematicsList title={t`Thematics`} /> <TopicsList title={t`Topics`} /> </Sidebar> diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx index a7adf89..b373041 100644 --- a/src/pages/sujet/[slug].tsx +++ b/src/pages/sujet/[slug].tsx @@ -13,10 +13,10 @@ import { } from '@services/graphql/queries'; import PostHeader from '@components/PostHeader/PostHeader'; import { ArticleMeta } from '@ts/types/articles'; -import ToC from '@components/ToC/ToC'; -import { RelatedThematics, TopicsList } from '@components/Widget'; +import { RelatedThematics, ToC, TopicsList } from '@components/Widgets'; import { useRef } from 'react'; import Head from 'next/head'; +import Sidebar from '@components/Sidebar/Sidebar'; const Subject: NextPageWithLayout<SubjectProps> = ({ subject }) => { const relatedThematics = useRef<ThematicPreview[]>([]); @@ -64,9 +64,9 @@ const Subject: NextPageWithLayout<SubjectProps> = ({ subject }) => { meta={meta} title={subject.title} /> - <aside className={styles.toc}> + <Sidebar position="left"> <ToC /> - </aside> + </Sidebar> <div className={styles.body}> <div dangerouslySetInnerHTML={{ __html: subject.content }}></div> {subject.posts.length > 0 && ( @@ -76,10 +76,10 @@ const Subject: NextPageWithLayout<SubjectProps> = ({ subject }) => { </section> )} </div> - <aside className={`${styles.aside} ${styles['aside--overflow']}`}> + <Sidebar position="right"> <RelatedThematics thematics={relatedThematics.current} /> <TopicsList title={t`Other topics`} /> - </aside> + </Sidebar> </article> </> ); diff --git a/src/pages/thematique/[slug].tsx b/src/pages/thematique/[slug].tsx index f23ad5b..4eee656 100644 --- a/src/pages/thematique/[slug].tsx +++ b/src/pages/thematique/[slug].tsx @@ -12,11 +12,11 @@ import { getThematicBySlug, } from '@services/graphql/queries'; import PostHeader from '@components/PostHeader/PostHeader'; -import ToC from '@components/ToC/ToC'; -import { RelatedTopics, ThematicsList } from '@components/Widget'; +import { RelatedTopics, ThematicsList, ToC } from '@components/Widgets'; import { useRef } from 'react'; import { ArticleMeta } from '@ts/types/articles'; import Head from 'next/head'; +import Sidebar from '@components/Sidebar/Sidebar'; const Thematic: NextPageWithLayout<ThematicProps> = ({ thematic }) => { const relatedSubjects = useRef<SubjectPreview[]>([]); @@ -58,9 +58,9 @@ const Thematic: NextPageWithLayout<ThematicProps> = ({ thematic }) => { className={`${styles.article} ${styles['article--no-comments']}`} > <PostHeader intro={thematic.intro} meta={meta} title={thematic.title} /> - <aside className={styles.toc}> + <Sidebar position="left"> <ToC /> - </aside> + </Sidebar> <div className={styles.body}> <div dangerouslySetInnerHTML={{ __html: thematic.content }}></div> {thematic.posts.length > 0 && ( @@ -70,10 +70,10 @@ const Thematic: NextPageWithLayout<ThematicProps> = ({ thematic }) => { </section> )} </div> - <aside className={`${styles.aside} ${styles['aside--overflow']}`}> + <Sidebar position="right"> <RelatedTopics topics={relatedSubjects.current} /> <ThematicsList title={t`Other thematics`} /> - </aside> + </Sidebar> </article> </> ); diff --git a/src/styles/pages/Page.module.scss b/src/styles/pages/Page.module.scss index 69b25c0..ac6646b 100644 --- a/src/styles/pages/Page.module.scss +++ b/src/styles/pages/Page.module.scss @@ -20,25 +20,6 @@ } } -.toc { - grid-column: 2; - - @include mix.media("screen") { - @include mix.dimensions("lg") { - grid-column: 1; - grid-row: 2 / 4; - align-self: stretch; - justify-self: end; - padding: 0 var(--spacing-sm); - - ol:first-of-type { - font-size: var(--font-size-sm); - font-weight: 500; - } - } - } -} - .list { @extend %reset-ordered-list; } @@ -60,13 +41,3 @@ li.item { grid-column: 2; } } - -.aside { - max-height: 100vh; - position: sticky; - top: 0; - - &--overflow { - overflow: auto; - } -} diff --git a/src/ts/types/app.ts b/src/ts/types/app.ts index d9bd041..b5707a2 100644 --- a/src/ts/types/app.ts +++ b/src/ts/types/app.ts @@ -57,6 +57,8 @@ export type RequestType = // Globals //============================================================================== +export type ButtonKind = 'primary' | 'secondary' | 'tertiary'; + export type ButtonPosition = 'left' | 'right' | 'center'; export type ContentParts = { @@ -95,3 +97,5 @@ export type PageInfo = { export type Slug = { slug: string; }; + +export type TitleLevel = 2 | 3 | 4 | 5 | 6; |
