From bd3015dcf05b4827de66da5796bf57cec7cd8ea8 Mon Sep 17 00:00:00 2001 From: Maximus <ten.maksim97@gmail.com> Date: Mon, 29 Aug 2022 13:37:46 +0300 Subject: [PATCH] added article with parts uses dot notation --- package.json | 5 +- src/components/Article/Article.tsx | 75 ++++++++++++++++ .../Article/ArticleParts/ArticleAuthors.tsx | 53 +++++++++++ .../ArticleParts/ArticleBreadcumbs.tsx | 31 +++++++ .../ArticleParts/ArticleDescription.tsx | 38 ++++++++ .../ArticleInteractionButtons.tsx | 87 +++++++++++++++++++ .../Article/ArticleParts/ArticleKeywords.tsx | 55 ++++++++++++ .../ArticleSubscriptionsButton.tsx | 39 +++++++++ .../Article/ArticleParts/ArticleTitle.tsx | 24 +++++ src/components/Article/ArticleSearch.tsx | 45 ++++++++++ tailwind.config.js | 77 ++++++++-------- 11 files changed, 491 insertions(+), 38 deletions(-) create mode 100644 src/components/Article/Article.tsx create mode 100644 src/components/Article/ArticleParts/ArticleAuthors.tsx create mode 100644 src/components/Article/ArticleParts/ArticleBreadcumbs.tsx create mode 100644 src/components/Article/ArticleParts/ArticleDescription.tsx create mode 100644 src/components/Article/ArticleParts/ArticleInteractionButtons.tsx create mode 100644 src/components/Article/ArticleParts/ArticleKeywords.tsx create mode 100644 src/components/Article/ArticleParts/ArticleSubscriptionsButton.tsx create mode 100644 src/components/Article/ArticleParts/ArticleTitle.tsx create mode 100644 src/components/Article/ArticleSearch.tsx diff --git a/package.json b/package.json index 3ba8665..0273842 100644 --- a/package.json +++ b/package.json @@ -27,11 +27,11 @@ "react-scripts": "5.0.1", "react-scrollbars-custom": "^4.1.0", "storybook-addon-pseudo-states": "^1.15.1", + "tailwindcss": "^3.1.7", "tsconfig-paths-webpack-plugin": "^4.0.0", "typescript": "^4.7.4", "web-vitals": "^2.1.4", - "yup": "^0.32.11", - "tailwindcss": "^3.1.7" + "yup": "^0.32.11" }, "scripts": { "dev-tools": "redux-devtools --hostname=localhost --port=8000", @@ -91,6 +91,7 @@ "@storybook/react": "^6.5.9", "@storybook/testing-library": "^0.0.13", "@svgr/webpack": "^6.3.1", + "@tailwindcss/line-clamp": "^0.4.0", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^13.5.0", diff --git a/src/components/Article/Article.tsx b/src/components/Article/Article.tsx new file mode 100644 index 0000000..9e10af2 --- /dev/null +++ b/src/components/Article/Article.tsx @@ -0,0 +1,75 @@ +import React, { useState } from "react"; +/* -------------------------------------------------------------------------- */ +/* imports Article parts */ +/* -------------------------------------------------------------------------- */ +import { ArticleTitle } from "./ArticleParts/ArticleTitle"; +import { ArticleBreadcumbs } from "./ArticleParts/ArticleBreadcumbs"; +import { ArticleAuthors } from "./ArticleParts/ArticleAuthors"; +import { ArticleKeywords } from "./ArticleParts/ArticleKeywords"; +import { ArticleInteractionButtons } from "./ArticleParts/ArticleInteractionButtons"; +import { ArticleDescription } from "./ArticleParts/ArticleDescription"; +import { ArticleSubscriptionsButtons } from "./ArticleParts/ArticleSubscriptionsButton"; + +/** + * Reduces a sequence of names to initials. + * @param {String} name Space Delimited sequence of names. + * @param {String} sep A period separating the initials. + * @param {String} trail A period ending the initials. + * @param {String} hyph A hypen separating double names. + * @return {String} Properly formatted initials. + */ +type ArticleTileExtentions = { + Title?: { + children?: string; + className?: string; + }; + Breadcumbs?: { + children?: string[]; + highlightLAstChild?: boolean; + }; + Authors?: { + children: React.ReactNode; + className?: string; + emphasis?: "low" | "high"; + }; + Keywords?: { + children?: React.ReactNode; + className?: string; + emphasis?: "low" | "high"; + }; + Description?: { + children?: React.ReactNode; + emphasis?: "low" | "high"; + isShowing?: boolean; + }; + InteractionButtons?: { + children?: React.ReactNode; + className?: string; + emphasis?: "high" | "low"; + }; + SubscriptionButtons?: { + className?: string; + }; +}; + +type ArticleTileProps = { + /** Description of prop "foo". */ + children?: React.ReactNode; +}; + +export function Article({ + /** Description of prop "foo". */ + children, +}: ArticleTileProps & ArticleTileExtentions) { + const [isShowing, setIsShowing] = useState(false); + + return <div className="flex flex-col w-full">{children}</div>; +} + +Article.Title = ArticleTitle; +Article.Breadcumbs = ArticleBreadcumbs; +Article.Authors = ArticleAuthors; +Article.Keywords = ArticleKeywords; +Article.InteractionButtons = ArticleInteractionButtons; +Article.Description = ArticleDescription; +Article.SubscriptionsButtons = ArticleSubscriptionsButtons; diff --git a/src/components/Article/ArticleParts/ArticleAuthors.tsx b/src/components/Article/ArticleParts/ArticleAuthors.tsx new file mode 100644 index 0000000..5e2e16d --- /dev/null +++ b/src/components/Article/ArticleParts/ArticleAuthors.tsx @@ -0,0 +1,53 @@ +import React, { useMemo } from "react"; +import { RouterLink } from "components/typography/RouterLink"; +import Typography from "components/typography/Typography"; +import classNames from "classnames"; +import { SVGUser } from "components/icons"; + +type AuthorsProps = { + children: React.ReactNode; + className?: string; + emphasis?: "low" | "high"; + linkTo?: string; +}; + +export function ArticleAuthors({ + children, + + className, + emphasis = "high", + linkTo = "#", +}: AuthorsProps) { + const authors = React.Children.map(children, (author, i) => { + return ( + <RouterLink to={linkTo}> + <Typography + fontWeightVariant={emphasis === "high" ? "medium" : "normal"} + className={classNames( + "hover:text-blue-600", + { + "text-xs leading-5 text-gray-500": emphasis === "low", + "text-lg leading-6 text-gray-900": emphasis === "high", + }, + { + "mr-1": i != React.Children.count(children) - 1, + }, + className + )} + > + {author} + {i != React.Children.count(children) - 1 ? "," : null} + </Typography> + </RouterLink> + ); + }); + + return ( + <div className="flex flex-row items-center"> + <SVGUser className="w-6 fill-gray-500 stroke-gray-500"></SVGUser> + <div className="ml-2 flex flex-row">{authors}</div> + </div> + ); +} + +ArticleAuthors.displayName = "ArticleAuthors"; diff --git a/src/components/Article/ArticleParts/ArticleBreadcumbs.tsx b/src/components/Article/ArticleParts/ArticleBreadcumbs.tsx new file mode 100644 index 0000000..e52881e --- /dev/null +++ b/src/components/Article/ArticleParts/ArticleBreadcumbs.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import Breadcrumbs from "components/breadcrumbs"; +import Logo from "components/Logo"; +import classNames from "classnames"; + +type ArticleBreadcumbsProps = { + emphasis?: "high" | "low"; + children?: string[]; +}; + +export function ArticleBreadcumbs({ + children, + emphasis = "high", //Emphasis high uses when we display article page +}: ArticleBreadcumbsProps) { + return ( + <Breadcrumbs + divider="slash" + className={classNames( + "text-xs leading-4 text-gray-700 flex flex-row items-center", + { + "last:text-gray-900": emphasis === "high", + } + )} + > + {emphasis === "high" ? <Logo className="w-4" fillColors="gray" /> : null} + {children} + </Breadcrumbs> + ); +} + +ArticleBreadcumbs.displayName = "ArticleBreadcumbs"; diff --git a/src/components/Article/ArticleParts/ArticleDescription.tsx b/src/components/Article/ArticleParts/ArticleDescription.tsx new file mode 100644 index 0000000..119241d --- /dev/null +++ b/src/components/Article/ArticleParts/ArticleDescription.tsx @@ -0,0 +1,38 @@ +import React from "react"; +import Typography from "components/typography/Typography"; +import { Transition } from "@headlessui/react"; + +type ArticleDescriptionProps = { + children?: React.ReactNode; + emphasis?: "low" | "high"; + isShowing?: boolean; +}; + +export function ArticleDescription({ + children, + emphasis = "high", + isShowing = false, +}: ArticleDescriptionProps) { + return emphasis === "low" ? ( + <div className="overflow-hidden"> + <Transition + appear + show={isShowing} + enter="ease-in-out duration-200" + enterFrom="-translate-y-full" + enterTo="translate-y-0" + leave="ease-in-out duration-200" + leaveFrom="translate-y-0" + leaveTo="-translate-y-full" + > + <Typography className="text-base text-gray-900">{children}</Typography> + </Transition> + </div> + ) : ( + <div> + <Typography className="text-base text-gray-900">{children}</Typography> + </div> + ); +} + +ArticleDescription.displayName = "ArticleDescription"; diff --git a/src/components/Article/ArticleParts/ArticleInteractionButtons.tsx b/src/components/Article/ArticleParts/ArticleInteractionButtons.tsx new file mode 100644 index 0000000..ef69869 --- /dev/null +++ b/src/components/Article/ArticleParts/ArticleInteractionButtons.tsx @@ -0,0 +1,87 @@ +import React from "react"; +import { Button } from "components/Button/Button"; +import Typography from "components/typography/Typography"; +import { + SVGArrowDown, + SVGArrowUp, + SVGCite, + SVGFiletext, + SVGDownload, + SVGShare, + SVGFolder, +} from "components/icons"; +import classNames from "classnames"; + +const interactionButtonsStore = [ + { + icon: <SVGFiletext />, + title: "Read file", + buttonEmphasis: "high", + iconClassName: "h-6 fill-white stroke-white", + }, + { + icon: <SVGDownload />, + title: "Download", + buttonEmphasis: "low", + iconClassName: "w-6 fill-gray-900 stroke-gray-900", + }, + { + icon: <SVGCite />, + title: "Cite", + buttonEmphasis: "low", + iconClassName: "w-6 fill-gray-900 stroke-gray-900", + }, + { + icon: <SVGShare />, + title: "Share", + buttonEmphasis: "low", + iconClassName: "w-6 fill-gray-900 stroke-gray-900", + }, +]; + +type ArticleButtonProps = { + children?: React.ReactNode; + className?: string; + emphasis?: "high" | "low"; +} & Omit<React.ComponentPropsWithoutRef<"button">, "">; + +export function ArticleInteractionButtons({ + children, + className, + emphasis, //to change displaying of component + ...props +}: ArticleButtonProps) { + const abstractButton = ( + <Button emphasis="medium" className="text-sm leading-4 items-center px-3"> + <Typography fontWeightVariant="bold" className="pr-2"> + Abstract + </Typography> + <Button.Icon> + <SVGArrowDown className="w-4 fill-blue-700 stroke-blue-700" /> + </Button.Icon> + </Button> + ); + + const fileInteractionButtons = interactionButtonsStore.map((button) => { + return ( + <Button + emphasis={button.buttonEmphasis === "high" ? "high" : "low"} + className="h-max px-2" + > + <Button.Icon> + {React.cloneElement(button.icon, { className: button.iconClassName })} + </Button.Icon> + {emphasis === "high" ? <Typography>{button.title}</Typography> : null} + </Button> + ); + }); + + return ( + <div className="flex flex-row"> + {emphasis === "low" && !children ? abstractButton : null} + {children ? children : fileInteractionButtons} + </div> + ); +} + +ArticleInteractionButtons.displayName = "ArticleInteractionButtons"; diff --git a/src/components/Article/ArticleParts/ArticleKeywords.tsx b/src/components/Article/ArticleParts/ArticleKeywords.tsx new file mode 100644 index 0000000..01b5546 --- /dev/null +++ b/src/components/Article/ArticleParts/ArticleKeywords.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import Typography from "components/typography/Typography"; +import { SVGKey } from "components/icons"; +import { RouterLink } from "components/typography/RouterLink"; +import classNames from "classnames"; + +type KeywordsProps = { + children?: React.ReactNode; + className?: string; + emphasis?: "low" | "high"; + linkTo?: string; +}; + +export function ArticleKeywords({ + children, + className, + emphasis = "high", + linkTo = "#", +}: KeywordsProps) { + const keywords = React.Children.map(children, (keyword, i) => { + return ( + <RouterLink to={linkTo}> + <div + className={classNames( + "mr-1", + "hover:text-blue-600", + { + "text-xs text-gray-500 leading-5": emphasis === "low", + "text-base text-gray-900 px-2 border rounded hover:border-blue-600 border-gray-900": + emphasis === "high", + }, + className + )} + > + <Typography> + {keyword} + {i != React.Children.count(children) - 1 && emphasis === "low" + ? "," + : null} + </Typography> + </div> + </RouterLink> + ); + }); + return ( + <div className="flex flex-row items-center"> + {emphasis === "low" ? ( + <SVGKey className="w-6 fill-gray-500 stroke-gray-500" /> + ) : null} + <div className="flex flex-row ml-2">{keywords}</div> + </div> + ); +} + +ArticleKeywords.displayName = "ArticleKeywords"; diff --git a/src/components/Article/ArticleParts/ArticleSubscriptionsButton.tsx b/src/components/Article/ArticleParts/ArticleSubscriptionsButton.tsx new file mode 100644 index 0000000..ad6dd09 --- /dev/null +++ b/src/components/Article/ArticleParts/ArticleSubscriptionsButton.tsx @@ -0,0 +1,39 @@ +import React, { useState } from "react"; +import { Button } from "components/Button/Button"; +import { + SVGFavoriteFilled, + SVGFavoriteOutlined, + SVGFolder, +} from "components/icons"; + +const subscriptionStyles = "fill-gray-500 stroke-gray-500 w-6"; + +type ArticleSubscriptionsButtonsProps = { + className?: string; + favorite?: boolean; + //Todo create tracking subscriptions and onClick props +} & Omit<React.ComponentPropsWithoutRef<"button">, "">; + +export function ArticleSubscriptionsButtons({ + className, + favorite = false, +}: ArticleSubscriptionsButtonsProps) { + return ( + <div className="flex flex-row"> + <Button emphasis="low"> + <Button.Icon> + <SVGFolder className={subscriptionStyles} /> + </Button.Icon> + </Button> + <Button emphasis="low" onClick={() => {}}> + <Button.Icon> + {!favorite ? ( + <SVGFavoriteOutlined className={subscriptionStyles} /> + ) : ( + <SVGFavoriteFilled className="fill-blue-600 stroke-blue-600 w-6" /> + )} + </Button.Icon> + </Button> + </div> + ); +} diff --git a/src/components/Article/ArticleParts/ArticleTitle.tsx b/src/components/Article/ArticleParts/ArticleTitle.tsx new file mode 100644 index 0000000..7a5ae89 --- /dev/null +++ b/src/components/Article/ArticleParts/ArticleTitle.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import Typography from "components/typography/Typography"; +import { RouterLink } from "components/typography/RouterLink"; +type ArticleTitleProps = { + className?: string; + children?: string; + linkTo?: string; +}; + +export function ArticleTitle({ + className, + children, + linkTo = "#", +}: ArticleTitleProps) { + return ( + <RouterLink to={linkTo}> + <Typography fontWeightVariant="semibold" className={className}> + {children} + </Typography> + </RouterLink> + ); +} + +ArticleTitle.displayName = "ArticleTitle"; diff --git a/src/components/Article/ArticleSearch.tsx b/src/components/Article/ArticleSearch.tsx new file mode 100644 index 0000000..8b0e397 --- /dev/null +++ b/src/components/Article/ArticleSearch.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import { Article } from "./Article"; + +export function ArticleSearch() { + return ( + <Article> + <div className="flex flex-row justify-between"> + <Article.Breadcumbs emphasis="high"> + {["Yoda", "Lallalla", "Maxim"]} + </Article.Breadcumbs> + <Article.SubscriptionsButtons /> + </div> + <div className="flex flex-row"> + <Article.Title className="text-3xl">Yeanda lacreav</Article.Title> + </div> + + <Article.Authors emphasis="low"> + {["Reavap", "aldjfoa", "dkfjaoif"]} + </Article.Authors> + <Article.Keywords emphasis="low"> + {["porn", "development", "frontend"]} + </Article.Keywords> + + <Article.InteractionButtons emphasis="low"> + low + </Article.InteractionButtons> + <Article.Description> + {" "} + Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere quidem + provident temporibus! Fuga dolores placeat at voluptatem quia, vero + molestiae animi et itaque a, officia ullam expedita temporibus cum + deserunt.Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere + quidem provident temporibus! Fuga dolores placeat at voluptatem quia, + vero molestiae animi et itaque a, officia ullam expedita temporibus cum + deserunt.Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere + quidem provident temporibus! Fuga dolores placeat at voluptatem quia, + vero molestiae animi et itaque a, officia ullam expedita temporibus cum + deserunt.Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere + quidem provident temporibus! Fuga dolores placeat at voluptatem quia, + vero molestiae animi et itaque a, officia ullam expedita temporibus cum + deserunt. + </Article.Description> + </Article> + ); +} diff --git a/tailwind.config.js b/tailwind.config.js index f8b96c6..8f4d294 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -12,7 +12,9 @@ function Helper() { let obj = {}; obj[key] = this.withOpacityValue(`--color-${key}-text-base`); obj[`${key}-muted`] = this.withOpacityValue(`--color-${key}-text-muted`); - obj[`${key}-inverted`] = this.withOpacityValue(`--color-${key}-text-inverted`); + obj[`${key}-inverted`] = this.withOpacityValue( + `--color-${key}-text-inverted` + ); return obj; }; @@ -22,7 +24,9 @@ function Helper() { obj[key] = this.withOpacityValue(`--color-${key}`); obj[`${key}-disabled`] = this.withOpacityValue(`--color-${key}-disabled`); obj[`${key}-hover`] = this.withOpacityValue(`--color-${key}-hover`); - obj[`${key}-disabled-hover`] = this.withOpacityValue(`--color-${key}-disabled-hover`); + obj[`${key}-disabled-hover`] = this.withOpacityValue( + `--color-${key}-disabled-hover` + ); return obj; }; @@ -44,7 +48,7 @@ function Helper() { this.luminanced = function (key, additionalStops = []) { let obj = {}; - obj['50'] = this.withOpacityValue(`--color-${key}-50`); + obj["50"] = this.withOpacityValue(`--color-${key}-50`); for (let index = 0; index < additionalStops.length; index++) { const element = additionalStops[index]; obj[element] = this.withOpacityValue(`--color-${key}-${element}`); @@ -62,64 +66,65 @@ const ColorHelper = new Helper(); * @type { import('@types/tailwindcss/tailwind-config').TailwindConfig } */ module.exports = { - content: ['./src/**/*.{jsx,tsx}'], + content: ["./src/**/*.{jsx,tsx}"], plugins: [ function ({ addComponents }) { addComponents({ - '.container': { - maxWidth: '840px', - paddingLeft: '0.5rem', - paddingRight: '0.5rem', - '@screen sm': { - maxWidth: 'calc(100% - 30px)', + ".container": { + maxWidth: "840px", + paddingLeft: "0.5rem", + paddingRight: "0.5rem", + "@screen sm": { + maxWidth: "calc(100% - 30px)", }, - '@screen md': { - maxWidth: 'calc(100% - 30px)', + "@screen md": { + maxWidth: "calc(100% - 30px)", }, - '@screen lg': { - maxWidth: '840px', + "@screen lg": { + maxWidth: "840px", }, - '@screen xl': { - maxWidth: '840px', + "@screen xl": { + maxWidth: "840px", }, - '@screen 2xl': { - maxWidth: '840px', + "@screen 2xl": { + maxWidth: "840px", }, }, }); }, + require("@tailwindcss/line-clamp"), ], theme: { extend: { screens: { - tall: { raw: '(min-height: 1200px) and (orientation: portrait)' }, - skewed: { raw: '(max-height: 600px)' }, - "1.5xl": '1300px', + tall: { raw: "(min-height: 1200px) and (orientation: portrait)" }, + skewed: { raw: "(max-height: 600px)" }, + "1.5xl": "1300px", }, height: { - 'half-screen': '50vh', + "half-screen": "50vh", }, minHeight: { - 'half-screen': '50vh', + "half-screen": "50vh", }, colors: { - aside: 'var(--color-aside)', - main: 'var(--color-body)', - header: '#191D2B', - transparent: 'transparent', - current: 'currentColor', - white: '#ffffff', - black: '#000000', - serv: ColorHelper.luminanced('serv'), - blue: ColorHelper.luminanced('blue'), - gray: ColorHelper.luminanced('gray', [75]), + aside: "var(--color-aside)", + main: "var(--color-body)", + header: "#191D2B", + transparent: "transparent", + current: "currentColor", + white: "#ffffff", + black: "#000000", + serv: ColorHelper.luminanced("serv"), + blue: ColorHelper.luminanced("blue"), + gray: ColorHelper.luminanced("gray", [75]), }, gridTemplateColumns: { - 'layout': 'min-content 7fr', + layout: "min-content 7fr", }, gridTemplateRows: { - 'page': '3.5rem calc(100vh - 3.5rem)' - } + page: "3.5rem calc(100vh - 3.5rem)", + }, }, }, };