From d54388af2946a420507b35e313dbcc066b861581 Mon Sep 17 00:00:00 2001 From: Pavel Grinchenko <53296341+pavelgrinchenko@users.noreply.github.com> Date: Wed, 8 Apr 2020 23:05:54 +0300 Subject: [PATCH] Migrate codebase to Typescript and CSS Modules (#1106) * Move blog's components under Blog namespace * Restructure components * Allow to scroll smoothly in the scrollIntoLayout util function * Replace styled-reset to reset-css. Merge global styles. Use local classNames instead of global in few components * Refactor Home page components to TS * Remove duplicated component - TrySection and replace it with PromoSection * Refactor SubscribeSection to get rid of linter warning * Extend scroll utils * Create one common component for buttons with two rows: TwoRowButton. Use it everywhere on main page. Refactor DownloadButton component * Refactor Support page components to TS * Fix scroll for no-hash links * Disable trailing slash redirects for dev mode * Refactor Features page components to TS * Refactor Community page components to TS * Refactor Documentation layout components to TS * Refactor remaining Documentation components to TS * Refactor root html file to TS * Get rid of styled-components * Add util for handling keyboard events to follow DRY * Remove hack from SearchForm to avoiding loading conflict with mailchimp embed script (as it was removed before) * Refactor front utils to TS * Remove temporary rule from eslint config * Refactor Link component to use redirected urls as href * Remove !important rules from ShowOnly styles * Split utils by using on front and shared * Refactor gatsby-node to separate utils * Update @react/portal package. Remove our custom Portal component * Update linters and reformat code accordingly to new rules * Update babel & webpack related packages * Update react related packages * Update gatsby-related packages * Update remaining packages * Another try to fix hash navigation * Make tsconfig more stricter. Remove wrong eslint rule and refactor all warnings * Toggle mobile menu only in mobile version * Fix Algolia's dropdown position * Fix Get Started button for the mobile version of main page * Align buttons in PromoSection component for mobile view * Fix comments blog rendering * Fix share buttons overlapping * Fix line height for main page sections titles * Fix some styles for the main page * Fix styles of RightPanel * Change styles of docs abbr tooltip a little bit * Fix initial scrolling in the RightPanel * Fix wrong tagname * Fix scroll in the left sidebar * Move src/utils/node to src/gatsby * Fix animation for doc pages * Fix styles for abbr tooltip * Fix svg images in the blog Co-authored-by: Pavel Grinchenko --- .eslintrc.json | 12 +- .github/ISSUE_TEMPLATE/bug_report.md | 1 - .github/ISSUE_TEMPLATE/feature_request.md | 1 - .prettierignore | 1 + .prettierrc | 1 + .stylelintrc | 2 +- .tmp-empty-package/index.d.ts | 0 .tmp-empty-package/package.json | 6 - config/postcss/media.js | 1 + config/postcss/mixins.js | 27 + config/prismjs/dvc-hook.js | 1 + content/docs/command-reference/move.md | 4 +- gatsby-config.js | 3 +- gatsby-node.js | 260 +- package.json | 142 +- plugins/gatsby-remark-dvc-linker/index.js | 4 +- plugins/resize-image-plugin/index.js | 5 +- plugins/utils/convertHast.js | 8 +- postcss.config.js | 4 + .../{BlogFeed => Blog/Feed}/Item/index.tsx | 14 +- .../Feed}/Item/placeholder.svg | 0 .../Feed}/Item/styles.module.css | 0 .../{BlogFeed => Blog/Feed}/index.tsx | 12 +- .../{BlogFeed => Blog/Feed}/styles.module.css | 0 .../{BlogFeedMeta => Blog/FeedMeta}/index.tsx | 8 +- .../FeedMeta}/styles.module.css | 0 .../{BlogHome => Blog/Home}/index.tsx | 16 +- .../{BlogLayout => Blog/Layout}/index.tsx | 8 +- .../Layout}/styles.module.css | 0 .../{BlogPost => Blog/Post}/HeroPic/index.tsx | 8 +- .../Post}/HeroPic/styles.module.css | 0 src/components/Blog/Post/Markdown/index.tsx | 13 + .../Post}/Markdown/styles.module.css | 0 .../Post}/Share/icons/facebook.svg | 0 .../Post}/Share/icons/subscribe.svg | 0 .../Post}/Share/icons/twitter.svg | 0 .../{BlogPost => Blog/Post}/Share/index.tsx | 6 +- .../Post}/Share/styles.module.css | 0 .../{BlogPost => Blog/Post}/Tooltip/index.tsx | 10 +- .../Post}/Tooltip/styles.module.css | 0 .../{BlogPost => Blog/Post}/index.tsx | 28 +- .../{BlogPost => Blog/Post}/styles.module.css | 0 src/components/Blog/Tags/index.tsx | 30 + src/components/BlogPost/Markdown/index.tsx | 18 - src/components/BlogTags/index.tsx | 30 - src/components/Community/Block/index.js | 28 - src/components/Community/Block/index.tsx | 30 + .../Block/{styles.js => styles.module.css} | 67 +- .../Contribute/{index.js => index.tsx} | 88 +- src/components/Community/Events/index.js | 121 - src/components/Community/Events/index.tsx | 126 + src/components/Community/Events/styles.js | 17 - .../Community/Events/styles.module.css | 13 + src/components/Community/Hero/index.js | 29 - src/components/Community/Hero/index.tsx | 45 + src/components/Community/Hero/styles.js | 23 - .../Community/Hero/styles.module.css | 16 + src/components/Community/Learn/index.js | 264 -- src/components/Community/Learn/index.tsx | 275 ++ .../Learn/{styles.js => styles.module.css} | 6 +- src/components/Community/Meet/index.js | 255 -- src/components/Community/Meet/index.tsx | 248 ++ .../Meet/{styles.js => styles.module.css} | 40 +- src/components/Community/Section/index.js | 83 - src/components/Community/Section/index.tsx | 100 + src/components/Community/Section/styles.js | 106 - .../Community/Section/styles.module.css | 107 + src/components/Community/index.js | 32 - src/components/Community/index.tsx | 38 + src/components/Community/styles.js | 143 - src/components/Community/styles.module.css | 135 + src/components/Diagram/index.js | 165 - src/components/Diagram/styles.js | 187 -- .../Layout/SearchForm/index.tsx} | 34 +- .../Layout/SearchForm/styles.module.css} | 39 +- .../Documentation/Layout/SidebarMenu/index.js | 139 - .../Layout/SidebarMenu/index.tsx | 157 + .../Layout/SidebarMenu/styles.js | 99 - .../Layout/SidebarMenu/styles.module.css | 88 + src/components/Documentation/Layout/index.js | 53 - src/components/Documentation/Layout/index.tsx | 55 + .../Layout/{styles.js => styles.module.css} | 104 +- .../Markdown/Tooltip/DesktopView/index.tsx | 134 + .../Tooltip/DesktopView/styles.module.css | 77 + .../Markdown/Tooltip/MobileView/index.tsx | 80 + .../Tooltip/MobileView/styles.module.css | 49 + .../Documentation/Markdown/Tooltip/index.tsx | 58 + .../Documentation/Markdown/index.js | 150 - .../Documentation/Markdown/index.tsx | 141 + .../Markdown/{styles.js => styles.module.css} | 205 +- .../Documentation/RightPanel/index.js | 162 - .../Documentation/RightPanel/index.tsx | 178 ++ .../Documentation/RightPanel/styles.js | 79 - .../RightPanel/styles.module.css | 86 + .../Documentation/Tutorials/index.js | 40 - .../Documentation/Tutorials/styles.js | 37 - .../Documentation/TutorialsLinks/index.tsx | 46 + .../TutorialsLinks/styles.module.css | 21 + .../Documentation/{index.js => index.tsx} | 26 +- src/components/Documentation/styles.js | 31 - .../Documentation/styles.module.css | 33 + src/components/DownloadButton/index.js | 199 -- src/components/DownloadButton/index.tsx | 181 ++ src/components/DownloadButton/styles.js | 144 - .../DownloadButton/styles.module.css | 68 + src/components/Features/index.js | 156 - src/components/Features/index.tsx | 169 + src/components/Features/styles.js | 54 - src/components/Features/styles.module.css | 58 + src/components/FeaturesHero/index.js | 14 - src/components/FeaturesHero/styles.js | 19 - src/components/GithubLine/index.js | 30 - src/components/GithubLine/styles.js | 39 - src/components/Hamburger/index.tsx | 18 - src/components/HamburgerIcon/index.tsx | 18 + .../styles.module.css | 0 src/components/HamburgerMenu/index.tsx | 14 +- .../HamburgerMenu/styles.module.css | 4 + src/components/Hero/index.js | 16 - src/components/Hero/styles.js | 12 - src/components/HeroSection/index.tsx | 19 + src/components/HeroSection/styles.module.css | 3 + src/components/Home/Diagram/index.tsx | 161 + src/components/Home/Diagram/styles.module.css | 196 ++ .../Home/LandingHero/GithubLine/index.tsx | 33 + .../LandingHero/GithubLine/styles.module.css | 41 + src/components/Home/LandingHero/index.tsx | 114 + .../Home/LandingHero/styles.module.css | 165 + src/components/Home/LearnMore/index.tsx | 32 + .../Home/LearnMore/styles.module.css | 74 + .../Home/UseCases/CollapsibleText/index.tsx | 40 + .../CollapsibleText/styles.module.css | 16 + src/components/Home/UseCases/Video/index.tsx | 52 + .../Home/UseCases/Video/styles.module.css | 49 + src/components/Home/UseCases/index.tsx | 130 + .../Home/UseCases/styles.module.css | 90 + src/components/Home/index.js | 29 - src/components/Home/index.tsx | 52 + src/components/Home/styles.js | 9 - src/components/Home/styles.module.css | 3 + src/components/IconSet/index.tsx | 24 +- src/components/LandingHero/index.js | 120 - src/components/LandingHero/styles.js | 200 -- src/components/LayoutFooter/index.tsx | 5 +- .../{ => LayoutHeader}/Nav/index.tsx | 28 +- .../{ => LayoutHeader}/Nav/styles.module.css | 0 src/components/LayoutHeader/index.tsx | 27 +- src/components/LayoutHeader/styles.module.css | 12 +- .../LayoutWidthContainer/styles.module.css | 11 +- src/components/LearnMore/index.js | 27 - src/components/LearnMore/styles.js | 63 - src/components/Link/index.tsx | 98 +- src/components/MainLayout/index.tsx | 8 +- src/components/Modal/index.js | 11 - src/components/NotFound/index.tsx | 16 +- src/components/Page/base.css | 38 +- src/components/Page/index.tsx | 13 +- src/components/Page/styles.module.css | 8 +- src/components/Page/utils.ts | 26 +- src/components/PageContent/index.tsx | 12 +- src/components/Popover/index.js | 54 - src/components/Popover/styles.js | 17 - src/components/PromoSection/index.js | 34 - src/components/PromoSection/index.tsx | 42 + src/components/PromoSection/styles.js | 126 - src/components/PromoSection/styles.module.css | 105 + src/components/ShowOnly/index.tsx | 20 + src/components/ShowOnly/styles.module.css | 15 + src/components/Subscribe/index.tsx | 34 - .../form => SubscribeSection/Form}/index.tsx | 45 +- .../Form}/styles.module.css | 0 .../glyph-1.svg | 0 .../glyph-2.svg | 0 src/components/SubscribeSection/index.tsx | 29 + .../styles.module.css | 0 src/components/Support/Popover/index.tsx | 31 + .../Support/Popover/styles.module.css | 14 + src/components/Support/index.js | 130 - src/components/Support/index.tsx | 136 + src/components/Support/styles.js | 138 - src/components/Support/styles.module.css | 214 ++ src/components/TextCollapse/index.js | 35 - src/components/TextCollapse/styles.js | 18 - src/components/TextRotate/index.js | 132 - src/components/TextRotate/styles.js | 7 - src/components/Tooltip/DesktopView/index.js | 170 - src/components/Tooltip/DesktopView/styles.js | 58 - src/components/Tooltip/MobileView/index.js | 67 - src/components/Tooltip/MobileView/styles.js | 54 - src/components/Tooltip/index.js | 67 - src/components/TrySection/index.js | 28 - src/components/TrySection/styles.js | 106 - src/components/TwoRowsButton/index.tsx | 49 + .../TwoRowsButton/styles.module.css | 87 + src/components/UseCases/index.js | 130 - src/components/UseCases/styles.js | 101 - src/components/Video/index.js | 100 - src/components/Video/styles.js | 109 - src/consts.js | 3 - src/gatsby/blog.js | 151 + src/gatsby/common.js | 70 + src/gatsby/doc.js | 81 + src/{html.js => html.tsx} | 21 +- src/queries/posts.ts | 10 +- src/queries/siteMeta.ts | 9 +- src/server/middleware/redirects/index.js | 13 +- src/styles.js | 175 - src/templates/blog-home.tsx | 4 +- src/templates/blog-post.tsx | 4 +- src/templates/blog-tags.tsx | 4 +- src/templates/doc-home.tsx | 2 +- src/typings.ts | 28 + src/utils/format.js | 7 - src/utils/{ => front}/api.ts | 40 +- src/utils/front/breakpoints.ts | 11 + src/utils/front/customProperties.ts | 31 + src/utils/front/ga.ts | 46 + src/utils/front/i18n.ts | 23 + src/utils/{images.js => front/images.ts} | 11 +- src/utils/{isClient.js => front/isClient.ts} | 2 +- src/utils/front/keyboard.ts | 9 + src/utils/front/resources.ts | 64 + src/utils/front/scroll.ts | 61 + src/utils/ga.js | 22 - src/utils/i18n.js | 20 - src/utils/paginatablePageGenerator.js | 50 - src/utils/resources.js | 37 - src/utils/scroll.ts | 11 - src/utils/{ => shared}/redirects.js | 7 +- src/utils/{ => shared}/redirects.test.js | 0 src/utils/{ => shared}/sidebar.js | 2 +- src/utils/{ => shared}/sidebar.test.js | 28 +- src/utils/shared/tagToSlug.js | 2 + src/utils/tagToSlug.js | 6 - tsconfig.json | 10 +- yarn.lock | 2839 +++++++++-------- 236 files changed, 7829 insertions(+), 7727 deletions(-) delete mode 100644 .tmp-empty-package/index.d.ts delete mode 100644 .tmp-empty-package/package.json rename src/components/{BlogFeed => Blog/Feed}/Item/index.tsx (93%) rename src/components/{BlogFeed => Blog/Feed}/Item/placeholder.svg (100%) rename src/components/{BlogFeed => Blog/Feed}/Item/styles.module.css (100%) rename src/components/{BlogFeed => Blog/Feed}/index.tsx (83%) rename src/components/{BlogFeed => Blog/Feed}/styles.module.css (100%) rename src/components/{BlogFeedMeta => Blog/FeedMeta}/index.tsx (85%) rename src/components/{BlogFeedMeta => Blog/FeedMeta}/styles.module.css (100%) rename src/components/{BlogHome => Blog/Home}/index.tsx (64%) rename src/components/{BlogLayout => Blog/Layout}/index.tsx (80%) rename src/components/{BlogLayout => Blog/Layout}/styles.module.css (100%) rename src/components/{BlogPost => Blog/Post}/HeroPic/index.tsx (81%) rename src/components/{BlogPost => Blog/Post}/HeroPic/styles.module.css (100%) create mode 100644 src/components/Blog/Post/Markdown/index.tsx rename src/components/{BlogPost => Blog/Post}/Markdown/styles.module.css (100%) rename src/components/{BlogPost => Blog/Post}/Share/icons/facebook.svg (100%) rename src/components/{BlogPost => Blog/Post}/Share/icons/subscribe.svg (100%) rename src/components/{BlogPost => Blog/Post}/Share/icons/twitter.svg (100%) rename src/components/{BlogPost => Blog/Post}/Share/index.tsx (92%) rename src/components/{BlogPost => Blog/Post}/Share/styles.module.css (100%) rename src/components/{BlogPost => Blog/Post}/Tooltip/index.tsx (89%) rename src/components/{BlogPost => Blog/Post}/Tooltip/styles.module.css (100%) rename src/components/{BlogPost => Blog/Post}/index.tsx (83%) rename src/components/{BlogPost => Blog/Post}/styles.module.css (100%) create mode 100644 src/components/Blog/Tags/index.tsx delete mode 100644 src/components/BlogPost/Markdown/index.tsx delete mode 100644 src/components/BlogTags/index.tsx delete mode 100644 src/components/Community/Block/index.js create mode 100644 src/components/Community/Block/index.tsx rename src/components/Community/Block/{styles.js => styles.module.css} (59%) rename src/components/Community/Contribute/{index.js => index.tsx} (60%) delete mode 100644 src/components/Community/Events/index.js create mode 100644 src/components/Community/Events/index.tsx delete mode 100644 src/components/Community/Events/styles.js create mode 100644 src/components/Community/Events/styles.module.css delete mode 100644 src/components/Community/Hero/index.js create mode 100644 src/components/Community/Hero/index.tsx delete mode 100644 src/components/Community/Hero/styles.js create mode 100644 src/components/Community/Hero/styles.module.css delete mode 100644 src/components/Community/Learn/index.js create mode 100644 src/components/Community/Learn/index.tsx rename src/components/Community/Learn/{styles.js => styles.module.css} (55%) delete mode 100644 src/components/Community/Meet/index.js create mode 100644 src/components/Community/Meet/index.tsx rename src/components/Community/Meet/{styles.js => styles.module.css} (54%) delete mode 100644 src/components/Community/Section/index.js create mode 100644 src/components/Community/Section/index.tsx delete mode 100644 src/components/Community/Section/styles.js create mode 100644 src/components/Community/Section/styles.module.css delete mode 100644 src/components/Community/index.js create mode 100644 src/components/Community/index.tsx delete mode 100644 src/components/Community/styles.js create mode 100644 src/components/Community/styles.module.css delete mode 100644 src/components/Diagram/index.js delete mode 100644 src/components/Diagram/styles.js rename src/components/{SearchForm/index.js => Documentation/Layout/SearchForm/index.tsx} (63%) rename src/components/{SearchForm/styles.js => Documentation/Layout/SearchForm/styles.module.css} (67%) delete mode 100644 src/components/Documentation/Layout/SidebarMenu/index.js create mode 100644 src/components/Documentation/Layout/SidebarMenu/index.tsx delete mode 100644 src/components/Documentation/Layout/SidebarMenu/styles.js create mode 100644 src/components/Documentation/Layout/SidebarMenu/styles.module.css delete mode 100644 src/components/Documentation/Layout/index.js create mode 100644 src/components/Documentation/Layout/index.tsx rename src/components/Documentation/Layout/{styles.js => styles.module.css} (55%) create mode 100644 src/components/Documentation/Markdown/Tooltip/DesktopView/index.tsx create mode 100644 src/components/Documentation/Markdown/Tooltip/DesktopView/styles.module.css create mode 100644 src/components/Documentation/Markdown/Tooltip/MobileView/index.tsx create mode 100644 src/components/Documentation/Markdown/Tooltip/MobileView/styles.module.css create mode 100644 src/components/Documentation/Markdown/Tooltip/index.tsx delete mode 100644 src/components/Documentation/Markdown/index.js create mode 100644 src/components/Documentation/Markdown/index.tsx rename src/components/Documentation/Markdown/{styles.js => styles.module.css} (75%) delete mode 100644 src/components/Documentation/RightPanel/index.js create mode 100644 src/components/Documentation/RightPanel/index.tsx delete mode 100644 src/components/Documentation/RightPanel/styles.js create mode 100644 src/components/Documentation/RightPanel/styles.module.css delete mode 100644 src/components/Documentation/Tutorials/index.js delete mode 100644 src/components/Documentation/Tutorials/styles.js create mode 100644 src/components/Documentation/TutorialsLinks/index.tsx create mode 100644 src/components/Documentation/TutorialsLinks/styles.module.css rename src/components/Documentation/{index.js => index.tsx} (62%) delete mode 100644 src/components/Documentation/styles.js create mode 100644 src/components/Documentation/styles.module.css delete mode 100644 src/components/DownloadButton/index.js create mode 100644 src/components/DownloadButton/index.tsx delete mode 100644 src/components/DownloadButton/styles.js create mode 100644 src/components/DownloadButton/styles.module.css delete mode 100644 src/components/Features/index.js create mode 100644 src/components/Features/index.tsx delete mode 100644 src/components/Features/styles.js create mode 100644 src/components/Features/styles.module.css delete mode 100644 src/components/FeaturesHero/index.js delete mode 100644 src/components/FeaturesHero/styles.js delete mode 100644 src/components/GithubLine/index.js delete mode 100644 src/components/GithubLine/styles.js delete mode 100644 src/components/Hamburger/index.tsx create mode 100644 src/components/HamburgerIcon/index.tsx rename src/components/{Hamburger => HamburgerIcon}/styles.module.css (100%) delete mode 100644 src/components/Hero/index.js delete mode 100644 src/components/Hero/styles.js create mode 100644 src/components/HeroSection/index.tsx create mode 100644 src/components/HeroSection/styles.module.css create mode 100644 src/components/Home/Diagram/index.tsx create mode 100644 src/components/Home/Diagram/styles.module.css create mode 100644 src/components/Home/LandingHero/GithubLine/index.tsx create mode 100644 src/components/Home/LandingHero/GithubLine/styles.module.css create mode 100644 src/components/Home/LandingHero/index.tsx create mode 100644 src/components/Home/LandingHero/styles.module.css create mode 100644 src/components/Home/LearnMore/index.tsx create mode 100644 src/components/Home/LearnMore/styles.module.css create mode 100644 src/components/Home/UseCases/CollapsibleText/index.tsx create mode 100644 src/components/Home/UseCases/CollapsibleText/styles.module.css create mode 100644 src/components/Home/UseCases/Video/index.tsx create mode 100644 src/components/Home/UseCases/Video/styles.module.css create mode 100644 src/components/Home/UseCases/index.tsx create mode 100644 src/components/Home/UseCases/styles.module.css delete mode 100644 src/components/Home/index.js create mode 100644 src/components/Home/index.tsx delete mode 100644 src/components/Home/styles.js create mode 100644 src/components/Home/styles.module.css delete mode 100644 src/components/LandingHero/index.js delete mode 100644 src/components/LandingHero/styles.js rename src/components/{ => LayoutHeader}/Nav/index.tsx (76%) rename src/components/{ => LayoutHeader}/Nav/styles.module.css (100%) delete mode 100644 src/components/LearnMore/index.js delete mode 100644 src/components/LearnMore/styles.js delete mode 100644 src/components/Modal/index.js delete mode 100644 src/components/Popover/index.js delete mode 100644 src/components/Popover/styles.js delete mode 100644 src/components/PromoSection/index.js create mode 100644 src/components/PromoSection/index.tsx delete mode 100644 src/components/PromoSection/styles.js create mode 100644 src/components/PromoSection/styles.module.css create mode 100644 src/components/ShowOnly/index.tsx create mode 100644 src/components/ShowOnly/styles.module.css delete mode 100644 src/components/Subscribe/index.tsx rename src/components/{Subscribe/form => SubscribeSection/Form}/index.tsx (58%) rename src/components/{Subscribe/form => SubscribeSection/Form}/styles.module.css (100%) rename src/components/{Subscribe => SubscribeSection}/glyph-1.svg (100%) rename src/components/{Subscribe => SubscribeSection}/glyph-2.svg (100%) create mode 100644 src/components/SubscribeSection/index.tsx rename src/components/{Subscribe => SubscribeSection}/styles.module.css (100%) create mode 100644 src/components/Support/Popover/index.tsx create mode 100644 src/components/Support/Popover/styles.module.css delete mode 100644 src/components/Support/index.js create mode 100644 src/components/Support/index.tsx delete mode 100644 src/components/Support/styles.js create mode 100644 src/components/Support/styles.module.css delete mode 100644 src/components/TextCollapse/index.js delete mode 100644 src/components/TextCollapse/styles.js delete mode 100644 src/components/TextRotate/index.js delete mode 100644 src/components/TextRotate/styles.js delete mode 100644 src/components/Tooltip/DesktopView/index.js delete mode 100644 src/components/Tooltip/DesktopView/styles.js delete mode 100644 src/components/Tooltip/MobileView/index.js delete mode 100644 src/components/Tooltip/MobileView/styles.js delete mode 100644 src/components/Tooltip/index.js delete mode 100644 src/components/TrySection/index.js delete mode 100644 src/components/TrySection/styles.js create mode 100644 src/components/TwoRowsButton/index.tsx create mode 100644 src/components/TwoRowsButton/styles.module.css delete mode 100644 src/components/UseCases/index.js delete mode 100644 src/components/UseCases/styles.js delete mode 100644 src/components/Video/index.js delete mode 100644 src/components/Video/styles.js create mode 100644 src/gatsby/blog.js create mode 100644 src/gatsby/common.js create mode 100644 src/gatsby/doc.js rename src/{html.js => html.tsx} (68%) delete mode 100644 src/styles.js delete mode 100644 src/utils/format.js rename src/utils/{ => front}/api.ts (64%) create mode 100644 src/utils/front/breakpoints.ts create mode 100644 src/utils/front/customProperties.ts create mode 100644 src/utils/front/ga.ts create mode 100644 src/utils/front/i18n.ts rename src/utils/{images.js => front/images.ts} (52%) rename src/utils/{isClient.js => front/isClient.ts} (82%) create mode 100644 src/utils/front/keyboard.ts create mode 100644 src/utils/front/resources.ts create mode 100644 src/utils/front/scroll.ts delete mode 100644 src/utils/ga.js delete mode 100644 src/utils/i18n.js delete mode 100644 src/utils/paginatablePageGenerator.js delete mode 100644 src/utils/resources.js delete mode 100644 src/utils/scroll.ts rename src/utils/{ => shared}/redirects.js (87%) rename src/utils/{ => shared}/redirects.test.js (100%) rename src/utils/{ => shared}/sidebar.js (98%) rename src/utils/{ => shared}/sidebar.test.js (91%) create mode 100644 src/utils/shared/tagToSlug.js delete mode 100644 src/utils/tagToSlug.js diff --git a/.eslintrc.json b/.eslintrc.json index 4d3b4b384d..5fdd13c338 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -28,20 +28,22 @@ { "prefixWithI": "always" } ], "no-tabs": "error", - "react/prop-types": "off", // Props should be described as TS types - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/no-var-requires": "warn" + "react/prop-types": "off" // Props should be described as TS types }, "overrides": [ { "files": [ - "src/utils/**/*.?(js|ts)", + "src/utils/shared/*.?(js|ts)", + "src/gatsby/**/*.js", + "src/components/PageWrapper/index.js", "scripts/**/*.js", "src/server/**/*.js", + "plugins/**/*.js", "gatsby-*.js" ], "rules": { - "@typescript-eslint/no-var-requires": "off" + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/explicit-function-return-type": "off" } } ] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index ce2b81b137..eff768bebc 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,5 +1,4 @@ --- name: Bug or improvement report about: Create a report to help us improve - --- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 725f7ddaa3..c709e39ab1 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,5 +1,4 @@ --- name: Feature request about: Suggest an idea for this project - --- diff --git a/.prettierignore b/.prettierignore index 6f4cc14699..1b9f04f14f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,3 @@ .cache/ +.github public/ diff --git a/.prettierrc b/.prettierrc index ec85921852..365b1903fa 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,6 @@ { "semi": false, + "arrowParens": "avoid", "singleQuote": true, "trailingComma": "none", "printWidth": 80, diff --git a/.stylelintrc b/.stylelintrc index 2be439f4d4..063efbba39 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -9,7 +9,7 @@ "ignoreProperties": ["composes"] }], "selector-pseudo-class-no-unknown": [true, { - "ignorePseudoClasses": ["global"] + "ignorePseudoClasses": ["global", "local"] }], "at-rule-no-unknown": [true, { "ignoreAtRules": ["mixin"] diff --git a/.tmp-empty-package/index.d.ts b/.tmp-empty-package/index.d.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/.tmp-empty-package/package.json b/.tmp-empty-package/package.json deleted file mode 100644 index 32a2f44040..0000000000 --- a/.tmp-empty-package/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "empty-package", - "version": "1.0.0", - "main": "index.js", - "license": "MIT" -} diff --git a/config/postcss/media.js b/config/postcss/media.js index 31d785fc05..1e30babccc 100644 --- a/config/postcss/media.js +++ b/config/postcss/media.js @@ -7,6 +7,7 @@ const screens = { } module.exports = { + screens, customMedia: { '--xxs-scr': `(max-width: ${screens.phone}px)`, '--xs-scr': `(max-width: ${screens.phablet}px)`, diff --git a/config/postcss/mixins.js b/config/postcss/mixins.js index 7ef3403a25..93d333560f 100644 --- a/config/postcss/mixins.js +++ b/config/postcss/mixins.js @@ -1,3 +1,5 @@ +const { customMedia } = require('./media') + const focus = { '&:focus': { color: 'var(--color-orange)', @@ -75,6 +77,31 @@ module.exports = { 'font-size': '16px', 'line-height': '25px' }, + columns: { + display: 'flex', + 'flex-direction': 'row', + 'flex-flow': 'wrap', + 'justify-content': 'space-between', + + [`@media ${customMedia['--sm-scr']}`]: { + 'flex-direction': 'row' + }, + + [`@media ${customMedia['--xs-scr']}`]: { + 'justify-content': 'center' + } + }, + column: { + 'flex-basis': '33.3%', + + [`@media ${customMedia['--sm-scr']}`]: { + 'flex-basis': '50%' + }, + + [`@media ${customMedia['--xs-scr']}`]: { + 'flex-basis': '100%' + } + }, link: { 'text-decoration': 'none', color: 'var(--color-blue)', diff --git a/config/prismjs/dvc-hook.js b/config/prismjs/dvc-hook.js index 89caa435c0..aa637fc7e8 100644 --- a/config/prismjs/dvc-hook.js +++ b/config/prismjs/dvc-hook.js @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-env node */ const Prism = require('prismjs') diff --git a/content/docs/command-reference/move.md b/content/docs/command-reference/move.md index 7b8c63411d..52c9b12356 100644 --- a/content/docs/command-reference/move.md +++ b/content/docs/command-reference/move.md @@ -140,8 +140,8 @@ $ tree ## Example: change an imported directory name and location -Let's try the same with an entire directory imported from an external DVC -repository with `dvc import`. Note that, as in the previous cases, the +Let's try the same with an entire directory imported from an external DVC +repository with `dvc import`. Note that, as in the previous cases, the DVC-file is also moved. ```dvc diff --git a/gatsby-config.js b/gatsby-config.js index 4780c89fd6..192d7e173d 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -30,7 +30,6 @@ const plugins = [ } }, 'gatsby-plugin-postcss', - 'gatsby-plugin-styled-components', 'gatsby-plugin-react-helmet', 'gatsby-plugin-sitemap', 'gatsby-plugin-twitter', @@ -60,7 +59,6 @@ const plugins = [ }, resolve: 'gatsby-remark-prismjs' }, - 'gatsby-remark-copy-linked-files', { resolve: 'gatsby-remark-smartypants', options: { @@ -74,6 +72,7 @@ const plugins = [ } }, 'gatsby-remark-relative-images', + 'gatsby-remark-copy-linked-files', { resolve: 'gatsby-remark-external-links' }, diff --git a/gatsby-node.js b/gatsby-node.js index 4c89947e85..9ab56b4c70 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -1,267 +1,31 @@ -/* eslint-env node */ - -const fs = require('fs') -const path = require('path') -const GithubSlugger = require('github-slugger') -const { createFilePath } = require('gatsby-source-filesystem') -const tagToSlug = require('./src/utils/tagToSlug') -const paginatablePageGenerator = require('./src/utils/paginatablePageGenerator') -const { siteMetadata } = require('./gatsby-config') - -const { getItemBySource } = require('./src/utils/sidebar') - -const remark = require('remark') -const remarkHTML = require('remark-html') - -const markdownToHtml = remark().use(remarkHTML).processSync -const slugger = new GithubSlugger() - -// Generate hedings data from markdown - -const SLUG_REGEXP = /\s+{#([a-z0-9-]*[a-z0-9]+)}\s*$/ - -function extractSlugFromTitle(title) { - // extracts expressions like {#too-many-files} from the end of a title - const meta = title.match(SLUG_REGEXP) - - if (meta) { - return [title.substring(0, meta.index), meta[1]] - } - return [title, slugger.slug(title)] -} - -const parseHeadings = text => { - const headingRegex = /\n(## \s*)(.*)/g - const matches = [] - let match - do { - match = headingRegex.exec(text) - if (match) { - const [title, slug] = extractSlugFromTitle(match[2]) - matches.push({ - text: title, - slug: slug - }) - } - } while (match) - - slugger.reset() - return matches -} +const { + getNodeSlug, + setPageContext, + removePageTrailingSlash +} = require('./src/gatsby/common') +const { createPages: createDocPages } = require('./src/gatsby/doc') +const { createPages: createBlogPages } = require('./src/gatsby/blog') exports.onCreateNode = ({ node, actions, getNode }) => { const { createNodeField } = actions if (node.internal.type === 'MarkdownRemark') { - // We need replace to fix paths for Windows - const contentPath = path.join(__dirname, 'content').replace(/\\/g, '/') - const source = node.fileAbsolutePath.replace(contentPath, '') - let value - - if (source.startsWith('/blog')) { - value = createFilePath({ - getNode, - node, - trailingSlash: false - }).replace(/^\/blog\/[0-9\-]*/, '/blog/') - - // Convert fields in frontmatter from markdown to html - const { - frontmatter: { descriptionLong, pictureComment } - } = node - - if (descriptionLong) { - node.frontmatter.descriptionLong = markdownToHtml( - descriptionLong - ).contents - } - - if (pictureComment) { - node.frontmatter.pictureComment = markdownToHtml( - pictureComment - ).contents - } - // end Convert fields - } else { - value = getItemBySource(source).path - } - createNodeField({ name: 'slug', node, - value + value: getNodeSlug(node, getNode) }) } } exports.createPages = async ({ graphql, actions }) => { - // DOCS - const docsResponse = await graphql( - ` - { - docs: allMarkdownRemark( - filter: { fileAbsolutePath: { regex: "/content/docs/" } } - limit: 9999 - ) { - edges { - node { - rawMarkdownBody - fields { - slug - } - } - } - } - } - ` - ) - - if (docsResponse.errors) { - throw docsResponse.errors - } - - const docComponent = path.resolve('./src/templates/doc-home.tsx') - - docsResponse.data.docs.edges.forEach(doc => { - const headings = parseHeadings(doc.node.rawMarkdownBody) - - if (doc.node.fields.slug) { - actions.createPage({ - component: docComponent, - path: doc.node.fields.slug, - context: { - isDocs: true, - slug: doc.node.fields.slug, - headings - } - }) - } - }) - - // Blog - const blogResponse = await graphql( - ` - { - allMarkdownRemark( - sort: { fields: [frontmatter___date], order: DESC } - filter: { fileAbsolutePath: { regex: "/content/blog/" } } - limit: 9999 - ) { - edges { - node { - fields { - slug - } - frontmatter { - title - } - } - } - } - home: allMarkdownRemark( - sort: { fields: [frontmatter___date], order: DESC } - filter: { fileAbsolutePath: { regex: "/content/blog/" } } - limit: 9999 - ) { - pageInfo { - itemCount - } - } - tags: allMarkdownRemark(limit: 9999) { - group(field: frontmatter___tags) { - fieldValue - pageInfo { - itemCount - } - } - } - } - ` - ) - - if (blogResponse.errors) { - throw blogResponse.errors - } - - // Create home blog pages (with pagination) - const blogHomeTemplate = path.resolve('./src/templates/blog-home.tsx') - - for (const page of paginatablePageGenerator({ - basePath: '/blog', - hasHeroItem: true, - itemCount: blogResponse.data.home.pageInfo.itemCount - })) { - actions.createPage({ - component: blogHomeTemplate, - path: page.path, - context: { - isBlog: true, - ...page.context - } - }) - } - - // Create blog posts pages - const blogPostTemplate = path.resolve('./src/templates/blog-post.tsx') - const posts = blogResponse.data.allMarkdownRemark.edges - - posts.forEach((post, index) => { - const previous = index === posts.length - 1 ? null : posts[index + 1].node - const next = index === 0 ? null : posts[index - 1].node - - actions.createPage({ - component: blogPostTemplate, - context: { - isBlog: true, - currentPage: index + 1, - next, - previous, - slug: post.node.fields.slug - }, - path: post.node.fields.slug - }) - }) - - // Create tags pages (with pagination) - const blogTagsTemplate = path.resolve('./src/templates/blog-tags.tsx') - - blogResponse.data.tags.group.forEach( - ({ fieldValue: tag, pageInfo: { itemCount } }) => { - const basePath = `/tags/${tagToSlug(tag)}` - - for (const page of paginatablePageGenerator({ basePath, itemCount })) { - actions.createPage({ - component: blogTagsTemplate, - path: page.path, - context: { tag, ...page.context } - }) - } - } - ) + createDocPages({ graphql, actions }) + createBlogPages({ graphql, actions }) } -const is404Regexp = /^\/404/ -const trailingSlashRegexp = /\/$/ - exports.onCreatePage = ({ page, actions }) => { - // Set necessary flags for pageContext - const newPage = { - ...page, - context: { - ...page.context, - is404: is404Regexp.test(page.path) - } - } - - // Remove trailing slash - if (page.path !== '/' && trailingSlashRegexp.test(newPage.path)) { - newPage.path = newPage.path.replace(trailingSlashRegexp, '') - } - - if (newPage !== page) { - actions.deletePage(page) - actions.createPage(newPage) - } + setPageContext(page, actions) + removePageTrailingSlash(page, actions) } // Ignore warnings about CSS inclusion order, because we use CSS modules. diff --git a/package.json b/package.json index 316479f009..0130b7f288 100644 --- a/package.json +++ b/package.json @@ -34,124 +34,126 @@ "dependencies": { "@hapi/wreck": "^17.0.0", "@octokit/graphql": "^4.3.1", - "@reach/portal": "^0.9.0", - "@reach/router": "^1.3.1", - "@reach/tooltip": "^0.9.1", - "@types/react": "^16.9.27", - "@types/styled-components": "^5.0.1", + "@reach/portal": "^0.10.0", + "@reach/router": "^1.3.3", + "@reach/tooltip": "^0.10.0", "classnames": "^2.2.6", "color": "^3.1.2", "compression": "^1.7.4", - "date-fns": "^2.8.1", + "date-fns": "^2.11.1", "docsearch.js": "^2.6.3", + "ease-component": "^1.0.0", "express": "^4.17.1", "fs-extra": "^9.0.0", - "gatsby": "^2.20.2", - "gatsby-image": "^2.3.0", - "gatsby-link": "^2.3.0", - "gatsby-plugin-feed": "^2.4.0", - "gatsby-plugin-typescript": "^2.2.5", - "gatsby-plugin-webpack-bundle-analyzer": "^1.0.5", - "github-markdown-css": "^3.0.1", + "gatsby": "^2.20.13", + "gatsby-image": "^2.3.1", + "gatsby-link": "^2.3.2", + "github-markdown-css": "^4.0.0", + "iso-url": "^0.4.7", "isomorphic-fetch": "^2.2.1", - "lodash.fill": "^3.4.0", "lodash.includes": "^4.3.0", "lodash.kebabcase": "^4.1.1", "lodash.startcase": "^4.4.0", "lodash.throttle": "^4.1.1", "lodash.topairs": "^4.3.0", + "nanoid": "^3.0.2", "node-cache": "^5.1.0", - "perfect-scrollbar": "^1.4.0", - "prismjs": "^1.19.0", + "perfect-scrollbar": "^1.5.0", + "prismjs": "^1.20.0", "promise-polyfill": "^8.1.3", "prop-types": "^15.7.2", - "react": "^16.12.0", + "react": "^16.13.1", "react-collapse": "^5.0.1", "react-collapsible": "^2.7.0", - "react-dom": "^16.12.0", + "react-dom": "^16.13.1", "react-ga": "^2.7.0", "react-helmet": "^5.2.1", - "react-markdown": "^4.2.2", + "react-markdown": "^4.3.1", "react-popover": "^0.5.10", - "react-scroll": "^1.7.13", "react-slick": "^0.25.2", - "react-use": "^13.27.0", - "rehype-react": "^4.0.1", - "request": "^2.88.0", + "react-use": "^14.0.0", + "rehype-react": "^5.0.1", + "reset-css": "^5.0.1", "s3-client": "^4.4.2", + "scroll": "^3.0.1", "serve-handler": "^6.1.2", "slick-carousel": "^1.8.1", - "styled-components": "^4.4.1", - "styled-reset": "^4.0.8", "unist-util-visit": "2.0.2" }, "devDependencies": { - "@babel/core": "^7.7.7", - "@svgr/webpack": "^5.2.0", + "@babel/core": "^7.9.0", + "@svgr/webpack": "^5.3.1", "@types/classnames": "^2.2.10", "@types/isomorphic-fetch": "^0.0.35", + "@types/lodash.includes": "^4.3.6", + "@types/lodash.startcase": "^4.4.6", + "@types/lodash.throttle": "^4.1.6", + "@types/lodash.topairs": "^4.3.6", + "@types/promise-polyfill": "^6.0.3", + "@types/react": "^16.9.32", + "@types/react-collapse": "^5.0.0", + "@types/react-dom": "^16.9.6", "@types/react-helmet": "^5.0.15", - "@types/vfile-message": "^1.0.1", - "@typescript-eslint/eslint-plugin": "^2.24.0", - "@typescript-eslint/parser": "^2.24.0", - "autoprefixer": "^9.7.4", - "babel-eslint": "^10.0.3", - "babel-jest": "^24.9.0", - "babel-plugin-styled-components": "^1.10.7", + "@types/react-popover": "^0.5.3", + "@types/react-slick": "^0.23.4", + "@types/rehype-react": "^4.0.0", + "@typescript-eslint/eslint-plugin": "^2.27.0", + "@typescript-eslint/parser": "^2.27.0", + "autoprefixer": "^9.7.6", + "babel-eslint": "^10.1.0", + "babel-jest": "^25.2.6", "babel-plugin-transform-define": "^2.0.0", "babel-plugin-transform-object-assign": "^6.22.0", - "eslint": "^6.7.2", - "eslint-config-prettier": "^6.7.0", - "eslint-plugin-json": "^2.0.1", + "eslint": "^6.8.0", + "eslint-config-prettier": "^6.10.1", + "eslint-plugin-json": "^2.1.1", "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-prettier": "^3.1.1", - "eslint-plugin-react": "^7.17.0", - "gatsby-plugin-catch-links": "^2.1.26", - "gatsby-plugin-google-analytics": "^2.1.36", - "gatsby-plugin-manifest": "^2.2.42", - "gatsby-plugin-postcss": "^2.2.0", - "gatsby-plugin-react-helmet": "^3.1.22", + "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-react": "^7.19.0", + "gatsby-plugin-catch-links": "^2.2.1", + "gatsby-plugin-feed": "^2.4.1", + "gatsby-plugin-google-analytics": "^2.2.2", + "gatsby-plugin-manifest": "^2.3.3", + "gatsby-plugin-postcss": "^2.2.1", + "gatsby-plugin-react-helmet": "^3.2.1", "gatsby-plugin-sentry": "^1.0.1", - "gatsby-plugin-sharp": "^2.5.2", - "gatsby-plugin-sitemap": "^2.2.27", - "gatsby-plugin-styled-components": "^3.1.19", + "gatsby-plugin-sharp": "^2.5.4", + "gatsby-plugin-sitemap": "^2.3.1", "gatsby-plugin-svgr": "^2.0.2", - "gatsby-plugin-twitter": "^2.2.0", - "gatsby-plugin-typescript": "^2.2.5", + "gatsby-plugin-twitter": "^2.2.2", + "gatsby-plugin-typescript": "^2.3.1", "gatsby-plugin-webpack-bundle-analyzer": "^1.0.5", - "gatsby-remark-autolink-headers": "^2.1.24", - "gatsby-remark-copy-linked-files": "^2.1.37", + "gatsby-remark-autolink-headers": "^2.2.1", + "gatsby-remark-copy-linked-files": "^2.2.1", "gatsby-remark-embed-gist": "^1.1.9", - "gatsby-remark-embedder": "^1.14.0", + "gatsby-remark-embedder": "^2.0.0", "gatsby-remark-external-links": "^0.0.4", - "gatsby-remark-images": "^3.2.0", - "gatsby-remark-prismjs": "^3.3.31", + "gatsby-remark-images": "^3.2.2", + "gatsby-remark-prismjs": "^3.4.1", "gatsby-remark-relative-images": "0.2.3", - "gatsby-remark-responsive-iframe": "^2.3.0", - "gatsby-remark-smartypants": "^2.1.21", - "gatsby-source-filesystem": "^2.1.48", - "gatsby-transformer-remark": "^2.6.59", - "gatsby-transformer-sharp": "^2.4.0", + "gatsby-remark-responsive-iframe": "^2.3.1", + "gatsby-remark-smartypants": "^2.2.1", + "gatsby-source-filesystem": "^2.2.2", + "gatsby-transformer-remark": "^2.7.1", + "gatsby-transformer-sharp": "^2.4.4", "hast-util-select": "^4.0.0", - "husky": "^4.0.10", - "jest": "^24.9.0", - "lint-staged": "^10.0.0", + "husky": "^4.2.3", + "jest": "^25.2.7", + "lint-staged": "^10.1.2", + "postcss-color-mod-function": "^3.0.3", "postcss-custom-media": "^7.0.8", "postcss-custom-properties": "^9.1.1", "postcss-mixins": "^6.2.3", "postcss-nested": "^4.2.1", - "prettier": "^1.19.1", + "prettier": "^2.0.4", "rehype-parse": "^6.0.2", - "rehype-stringify": "^6.0.1", - "remark": "^11.0.2", - "remark-html": "^10.0.0", - "stylelint": "^13.2.1", + "rehype-stringify": "^7.0.0", + "remark": "^12.0.0", + "remark-html": "^11.0.1", + "stylelint": "^13.3.0", "stylelint-config-standard": "^20.0.0", "typescript": "^3.8.3" }, - "resolutions": { - "@types/react-native": "link:./.tmp-empty-package" - }, "husky": { "hooks": { "pre-commit": "yarn lint-staged" diff --git a/plugins/gatsby-remark-dvc-linker/index.js b/plugins/gatsby-remark-dvc-linker/index.js index 8f2a58f362..50d64bf7c6 100644 --- a/plugins/gatsby-remark-dvc-linker/index.js +++ b/plugins/gatsby-remark-dvc-linker/index.js @@ -1,14 +1,14 @@ /* eslint-env node */ const visit = require('unist-util-visit') -const { getItemByPath } = require('../../src/utils/sidebar') +const { getItemByPath } = require('../../src/utils/shared/sidebar') const DVC_REGEXP = /dvc\s+[a-z][a-z-.]*/ const COMMAND_REGEXP = /^[a-z][a-z-]*$/ const COMMAND_ROOT = '/doc/command-reference/' module.exports = ({ markdownAST }) => { - visit(markdownAST, 'inlineCode', function(node, index, parent) { + visit(markdownAST, 'inlineCode', function (node, index, parent) { if (parent.type !== 'link' && DVC_REGEXP.test(node.value)) { const parts = node.value.split(/\s+/) let url diff --git a/plugins/resize-image-plugin/index.js b/plugins/resize-image-plugin/index.js index 747b005e49..e47939ba6a 100644 --- a/plugins/resize-image-plugin/index.js +++ b/plugins/resize-image-plugin/index.js @@ -92,8 +92,9 @@ module.exports = ({ markdownAST }) => { if (wrap) { const { className, style } = wrapperImage.properties - wrapperImage.properties.className = `${className || - ''} ${imageWrapClassPrefix}${wrap}` + wrapperImage.properties.className = `${ + className || '' + } ${imageWrapClassPrefix}${wrap}` // Prevent us from using an !important in the CSS wrapperImage.properties.style = style.replace( diff --git a/plugins/utils/convertHast.js b/plugins/utils/convertHast.js index 395d41f707..353b6191fb 100644 --- a/plugins/utils/convertHast.js +++ b/plugins/utils/convertHast.js @@ -4,15 +4,11 @@ const stringify = require('rehype-stringify') /** HAST - Hypertext Abstract Syntax Tree */ function convertHtmlToHast(htmlString) { - return unified() - .use(parse, { fragment: true }) - .parse(htmlString) + return unified().use(parse, { fragment: true }).parse(htmlString) } function convertHastToHtml(htmlAst) { - return unified() - .use(stringify) - .stringify(htmlAst) + return unified().use(stringify).stringify(htmlAst) } module.exports = { diff --git a/postcss.config.js b/postcss.config.js index 86c3e1ba36..ca842e4956 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -3,6 +3,7 @@ const autoprefixer = require('autoprefixer') const customMedia = require('postcss-custom-media') const customProperties = require('postcss-custom-properties') const mixins = require('postcss-mixins') +const colorMod = require('postcss-color-mod-function') const mediaConfig = require('./config/postcss/media') const mixinsConfig = require('./config/postcss/mixins') @@ -16,6 +17,9 @@ module.exports = function postcssConfig() { importFrom: ['src/components/Page/base.css'] }), nested, + colorMod({ + importFrom: ['src/components/Page/base.css'] + }), autoprefixer ] } diff --git a/src/components/BlogFeed/Item/index.tsx b/src/components/Blog/Feed/Item/index.tsx similarity index 93% rename from src/components/BlogFeed/Item/index.tsx rename to src/components/Blog/Feed/Item/index.tsx index acffe4e7cb..23a646cdf3 100644 --- a/src/components/BlogFeed/Item/index.tsx +++ b/src/components/Blog/Feed/Item/index.tsx @@ -1,17 +1,17 @@ import React, { useEffect, useRef } from 'react' import { useRafState, useWindowSize } from 'react-use' import { graphql } from 'gatsby' -import Link from '../../Link' +import Link from '../../../Link' import Image, { FixedObject, FluidObject } from 'gatsby-image' import cn from 'classnames' -import BlogFeedMeta from '../../BlogFeedMeta' +import FeedMeta from '../../FeedMeta' import styles from './styles.module.css' import { ReactComponent as Placeholder } from './placeholder.svg' -export interface IBlogFeedPostData { +export interface IBlogPostData { id: string timeToRead: string fields: { @@ -45,10 +45,10 @@ export interface IBlogFeedPostData { interface IBlogFeedItemProps { big?: boolean - feedPost: IBlogFeedPostData + feedPost: IBlogPostData } -const BlogFeedItem: React.SFC = ({ +const Item: React.SFC = ({ big, feedPost: { fields, frontmatter, timeToRead } }) => { @@ -97,7 +97,7 @@ const BlogFeedItem: React.SFC = ({
{description}
- } @@ -23,7 +23,7 @@ interface IBlogFeedProps { pageInfo: IPaginatorPageInfo } -const BlogFeed: React.SFC = ({ +const Feed: React.SFC = ({ feedPostList: { edges }, pageInfo, bigFirst = true, @@ -42,7 +42,7 @@ const BlogFeed: React.SFC = ({
{edges.map(({ node }, index) => ( - = ({ +const FeedMeta: React.SFC = ({ avatar, commentsUrl, commentsCount, @@ -47,4 +47,4 @@ const BlogFeedMeta: React.SFC = ({ ) } -export default BlogFeedMeta +export default FeedMeta diff --git a/src/components/BlogFeedMeta/styles.module.css b/src/components/Blog/FeedMeta/styles.module.css similarity index 100% rename from src/components/BlogFeedMeta/styles.module.css rename to src/components/Blog/FeedMeta/styles.module.css diff --git a/src/components/BlogHome/index.tsx b/src/components/Blog/Home/index.tsx similarity index 64% rename from src/components/BlogHome/index.tsx rename to src/components/Blog/Home/index.tsx index 551ddb620e..5d3c2d9610 100644 --- a/src/components/BlogHome/index.tsx +++ b/src/components/Blog/Home/index.tsx @@ -1,20 +1,20 @@ import React from 'react' -import { IPaginatorPageInfo } from '../Paginator' -import PageContent from '../PageContent' -import BlogFeed, { IBlogFeedPostList } from '../BlogFeed' -import Subscribe from '../Subscribe' +import { IPaginatorPageInfo } from '../../Paginator' +import PageContent from '../../PageContent' +import Feed, { IBlogFeedPostList } from '../Feed' +import SubscribeSection from '../../SubscribeSection' interface IBlogHomeProps { posts: IBlogFeedPostList pageInfo: IPaginatorPageInfo } -const BlogHome: React.SFC = ({ posts, pageInfo }) => { +const Home: React.SFC = ({ posts, pageInfo }) => { return ( <> - = ({ posts, pageInfo }) => { } /> - + ) } -export default BlogHome +export default Home diff --git a/src/components/BlogLayout/index.tsx b/src/components/Blog/Layout/index.tsx similarity index 80% rename from src/components/BlogLayout/index.tsx rename to src/components/Blog/Layout/index.tsx index bf1f5907ae..8371ea95b6 100644 --- a/src/components/BlogLayout/index.tsx +++ b/src/components/Blog/Layout/index.tsx @@ -1,6 +1,6 @@ import React from 'react' -import SEO from '../SEO' -import MainLayout, { LayoutComponent } from '../MainLayout' +import SEO from '../../SEO' +import MainLayout, { LayoutComponent } from '../../MainLayout' import styles from './styles.module.css' @@ -9,7 +9,7 @@ const keywords = const description = 'Data Version Control Blog. We write about machine learning workflow. From data versioning and processing to model productionization. We share our news, findings, interesting reads, community takeaways.' -const BlogLayout: LayoutComponent = ({ children, ...restProps }) => ( +const Layout: LayoutComponent = ({ children, ...restProps }) => ( ( ) -export default BlogLayout +export default Layout diff --git a/src/components/BlogLayout/styles.module.css b/src/components/Blog/Layout/styles.module.css similarity index 100% rename from src/components/BlogLayout/styles.module.css rename to src/components/Blog/Layout/styles.module.css diff --git a/src/components/BlogPost/HeroPic/index.tsx b/src/components/Blog/Post/HeroPic/index.tsx similarity index 81% rename from src/components/BlogPost/HeroPic/index.tsx rename to src/components/Blog/Post/HeroPic/index.tsx index 5be49f95e5..f11ac66e9e 100644 --- a/src/components/BlogPost/HeroPic/index.tsx +++ b/src/components/Blog/Post/HeroPic/index.tsx @@ -1,15 +1,15 @@ import Image from 'gatsby-image' import React from 'react' -import { BLOG } from '../../../consts' +import { BLOG } from '../../../../consts' import { IBlogPostHeroPic, IGatsbyImageProps -} from '../../../templates/blog-post' +} from '../../../../templates/blog-post' import styles from './styles.module.css' -function NonStretchedImage(props: IGatsbyImageProps) { +const NonStretchedImage: React.SFC = props => { let normalizedProps = props if (props.fluid && props.fluid.presentationWidth) { const presetantionWidth = props.fluid?.presentationWidth @@ -29,7 +29,7 @@ function NonStretchedImage(props: IGatsbyImageProps) { return } -function HeroPic({ pictureComment, picture }: IBlogPostHeroPic) { +const HeroPic: React.SFC = ({ pictureComment, picture }) => { return (
diff --git a/src/components/BlogPost/HeroPic/styles.module.css b/src/components/Blog/Post/HeroPic/styles.module.css similarity index 100% rename from src/components/BlogPost/HeroPic/styles.module.css rename to src/components/Blog/Post/HeroPic/styles.module.css diff --git a/src/components/Blog/Post/Markdown/index.tsx b/src/components/Blog/Post/Markdown/index.tsx new file mode 100644 index 0000000000..abf82be6eb --- /dev/null +++ b/src/components/Blog/Post/Markdown/index.tsx @@ -0,0 +1,13 @@ +import React from 'react' + +import styles from './styles.module.css' + +interface IMarkdownProps { + html: string +} + +const Markdown: React.SFC = ({ html }) => ( +
+) + +export default Markdown diff --git a/src/components/BlogPost/Markdown/styles.module.css b/src/components/Blog/Post/Markdown/styles.module.css similarity index 100% rename from src/components/BlogPost/Markdown/styles.module.css rename to src/components/Blog/Post/Markdown/styles.module.css diff --git a/src/components/BlogPost/Share/icons/facebook.svg b/src/components/Blog/Post/Share/icons/facebook.svg similarity index 100% rename from src/components/BlogPost/Share/icons/facebook.svg rename to src/components/Blog/Post/Share/icons/facebook.svg diff --git a/src/components/BlogPost/Share/icons/subscribe.svg b/src/components/Blog/Post/Share/icons/subscribe.svg similarity index 100% rename from src/components/BlogPost/Share/icons/subscribe.svg rename to src/components/Blog/Post/Share/icons/subscribe.svg diff --git a/src/components/BlogPost/Share/icons/twitter.svg b/src/components/Blog/Post/Share/icons/twitter.svg similarity index 100% rename from src/components/BlogPost/Share/icons/twitter.svg rename to src/components/Blog/Post/Share/icons/twitter.svg diff --git a/src/components/BlogPost/Share/index.tsx b/src/components/Blog/Post/Share/index.tsx similarity index 92% rename from src/components/BlogPost/Share/index.tsx rename to src/components/Blog/Post/Share/index.tsx index 4503bafa95..50a7d49634 100644 --- a/src/components/BlogPost/Share/index.tsx +++ b/src/components/Blog/Post/Share/index.tsx @@ -1,7 +1,7 @@ import { graphql, useStaticQuery } from 'gatsby' import React, { useCallback } from 'react' -import Link from '../../Link' +import Link from '../../../Link' import Tooltip from '../Tooltip' import { ReactComponent as Facebook } from './icons/facebook.svg' @@ -10,7 +10,7 @@ import { ReactComponent as Twitter } from './icons/twitter.svg' import styles from './styles.module.css' -function openWindow(e: React.MouseEvent, href: string) { +function openWindow(e: React.MouseEvent, href: string): void { e.preventDefault() window.open( @@ -26,7 +26,7 @@ interface IShareProps { slug: string } -function Share({ className, text, slug }: IShareProps) { +const Share: React.SFC = ({ className, text, slug }) => { const { site: { siteMetadata: { siteUrl } diff --git a/src/components/BlogPost/Share/styles.module.css b/src/components/Blog/Post/Share/styles.module.css similarity index 100% rename from src/components/BlogPost/Share/styles.module.css rename to src/components/Blog/Post/Share/styles.module.css diff --git a/src/components/BlogPost/Tooltip/index.tsx b/src/components/Blog/Post/Tooltip/index.tsx similarity index 89% rename from src/components/BlogPost/Tooltip/index.tsx rename to src/components/Blog/Post/Tooltip/index.tsx index 967ce55de1..8f2e3443c1 100644 --- a/src/components/BlogPost/Tooltip/index.tsx +++ b/src/components/Blog/Post/Tooltip/index.tsx @@ -29,7 +29,9 @@ const centered: Position = (triggerRect, tooltipRect) => { return result } -const portalStyle = (triggerRect: DOMRect | null) => { +const portalStyle = ( + triggerRect: DOMRect | null +): { left?: number; top?: number } => { if (!triggerRect) { return { left: 0, top: 0 } } @@ -42,7 +44,11 @@ const portalStyle = (triggerRect: DOMRect | null) => { } } -function ModifiedTooltip({ children, label, ariaLabel }: TooltipProps) { +const ModifiedTooltip: React.SFC = ({ + children, + label, + ariaLabel +}) => { const [trigger, tooltip] = useTooltip() const { isVisible, triggerRect } = tooltip diff --git a/src/components/BlogPost/Tooltip/styles.module.css b/src/components/Blog/Post/Tooltip/styles.module.css similarity index 100% rename from src/components/BlogPost/Tooltip/styles.module.css rename to src/components/Blog/Post/Tooltip/styles.module.css diff --git a/src/components/BlogPost/index.tsx b/src/components/Blog/Post/index.tsx similarity index 83% rename from src/components/BlogPost/index.tsx rename to src/components/Blog/Post/index.tsx index 369f0149b2..7756842cdf 100644 --- a/src/components/BlogPost/index.tsx +++ b/src/components/Blog/Post/index.tsx @@ -3,24 +3,24 @@ import cn from 'classnames' import React, { useMemo, useRef } from 'react' import { useWindowScroll, useWindowSize } from 'react-use' -import { IBlogPostData } from '../../templates/blog-post' +import { IBlogPostData } from '../../../templates/blog-post' -import { useCommentsCount } from '../../utils/api' -import { pluralizeComments } from '../../utils/i18n' -import tagToSlug from '../../utils/tagToSlug' +import { useCommentsCount } from '../../../utils/front/api' +import { pluralizeComments } from '../../../utils/front/i18n' +import tagToSlug from '../../../utils/shared/tagToSlug' import Markdown from './Markdown' -import BlogFeedMeta from '../BlogFeedMeta' -import Link from '../Link' -import PseudoButton from '../PseudoButton' +import FeedMeta from '../FeedMeta' +import Link from '../../Link' +import PseudoButton from '../../PseudoButton' import HeroPic from './HeroPic' import Share from './Share' -import PageContent from '../PageContent' -import Subscribe from '../Subscribe' +import PageContent from '../../PageContent' +import SubscribeSection from '../../SubscribeSection' import styles from './styles.module.css' -const BlogPost: React.SFC = ({ +const Post: React.SFC = ({ html, timeToRead, frontmatter, @@ -80,7 +80,7 @@ const BlogPost: React.SFC = ({ ) : (
{description}
)} - = ({ Discuss this post - {pluralizeComments(result)} + {pluralizeComments(result || 0)}
)}
- + ) } -export default BlogPost +export default Post diff --git a/src/components/BlogPost/styles.module.css b/src/components/Blog/Post/styles.module.css similarity index 100% rename from src/components/BlogPost/styles.module.css rename to src/components/Blog/Post/styles.module.css diff --git a/src/components/Blog/Tags/index.tsx b/src/components/Blog/Tags/index.tsx new file mode 100644 index 0000000000..1ceacb7374 --- /dev/null +++ b/src/components/Blog/Tags/index.tsx @@ -0,0 +1,30 @@ +import React from 'react' + +import { IPaginatorPageInfo } from '../../Paginator' +import PageContent from '../../PageContent' +import Feed, { IBlogFeedPostList } from '../Feed' +import SubscribeSection from '../../SubscribeSection' + +interface IBlogTagsProps { + posts: IBlogFeedPostList + pageInfo: IPaginatorPageInfo + header: string +} + +const Tags: React.SFC = ({ posts, pageInfo, header }) => { + return ( + <> + + + + + + ) +} + +export default Tags diff --git a/src/components/BlogPost/Markdown/index.tsx b/src/components/BlogPost/Markdown/index.tsx deleted file mode 100644 index 683bd58407..0000000000 --- a/src/components/BlogPost/Markdown/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' - -import styles from './styles.module.css' - -interface IMarkdownProps { - html: string -} - -function Markdown({ html }: IMarkdownProps) { - return ( -
- ) -} - -export default Markdown diff --git a/src/components/BlogTags/index.tsx b/src/components/BlogTags/index.tsx deleted file mode 100644 index bd1f411f73..0000000000 --- a/src/components/BlogTags/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react' - -import { IPaginatorPageInfo } from '../Paginator' -import PageContent from '../PageContent' -import BlogFeed, { IBlogFeedPostList } from '../BlogFeed' -import Subscribe from '../Subscribe' - -interface IBlogTagsProps { - posts: IBlogFeedPostList - pageInfo: IPaginatorPageInfo - header: string -} - -const BlogTags: React.SFC = ({ posts, pageInfo, header }) => { - return ( - <> - - - - - - ) -} - -export default BlogTags diff --git a/src/components/Community/Block/index.js b/src/components/Community/Block/index.js deleted file mode 100644 index 3aea85cd9d..0000000000 --- a/src/components/Community/Block/index.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import { Action, Content, Icon, Header, Wrapper } from './styles' - -export default function Block({ action, children, icon, title }) { - const hasAction = !!action - - return ( - - {title && ( -
- {title} - {icon && } -
- )} - {children} - {hasAction && {action}} -
- ) -} - -Block.propTypes = { - action: PropTypes.node, - children: PropTypes.node, - icon: PropTypes.string, - title: PropTypes.node -} diff --git a/src/components/Community/Block/index.tsx b/src/components/Community/Block/index.tsx new file mode 100644 index 0000000000..0762cc2928 --- /dev/null +++ b/src/components/Community/Block/index.tsx @@ -0,0 +1,30 @@ +import React from 'react' + +import styles from './styles.module.css' + +interface ICommunityBlockProps { + children: React.ReactNode + title?: React.ReactNode + action?: React.ReactNode + icon?: string +} + +const Block: React.SFC = ({ + title, + children, + action, + icon +}) => ( +
+ {title && ( +
+ {title} + {icon && } +
+ )} +
{children}
+ {action &&
{action}
} +
+) + +export default Block diff --git a/src/components/Community/Block/styles.js b/src/components/Community/Block/styles.module.css similarity index 59% rename from src/components/Community/Block/styles.js rename to src/components/Community/Block/styles.module.css index f0b65fbd75..9b5c865b31 100644 --- a/src/components/Community/Block/styles.js +++ b/src/components/Community/Block/styles.module.css @@ -1,53 +1,50 @@ -import styled from 'styled-components' -import { media } from '../../../styles' - -export const Action = styled.div` - margin-top: 20px; -` - -export const Content = styled.div` - flex-grow: 1; - font-size: 16px; - line-height: 24px; - color: #838d93; -` +.container { + box-sizing: border-box; + position: relative; + display: flex; + flex-direction: column; + justify-content: stretch; + width: 100%; + padding: 10px 20px 20px; + border-radius: 20px; + background: var(--color-light-blue); +} -export const Header = styled.div` +.header { display: flex; justify-content: space-between; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #d8dfe3; - font-family: BrandonGrotesque; + font-family: var(--font-brandon); font-size: 24px; font-weight: 500; line-height: 34px; - color: #24292e; + color: var(--color-black); - ${media.tablet` + @media (--sm-scr) { margin: 0; border: none; font-size: 20px; line-height: 30px; - `} -` + } +} -export const Icon = styled.img` +.action { + margin-top: 20px; +} + +.content { + flex-grow: 1; + font-size: 16px; + line-height: 24px; + color: var(--color-gray); +} + +.icon { margin: 0 -2px 0 0; - ${media.tablet` + @media (--sm-scr) { display: none; - `} -` - -export const Wrapper = styled.div` - box-sizing: border-box; - position: relative; - display: flex; - flex-direction: column; - justify-content: stretch; - width: 100%; - padding: 10px 20px 20px; - border-radius: 20px; - background: #eef4f8; -` + } +} diff --git a/src/components/Community/Contribute/index.js b/src/components/Community/Contribute/index.tsx similarity index 60% rename from src/components/Community/Contribute/index.js rename to src/components/Community/Contribute/index.tsx index ea3323df83..fc9b459822 100644 --- a/src/components/Community/Contribute/index.js +++ b/src/components/Community/Contribute/index.tsx @@ -1,25 +1,27 @@ import React from 'react' -import PropTypes from 'prop-types' - -import { logEvent } from '../../../utils/ga' +import { ICommunitySectionTheme } from '../' +import LayoutWidthContainer from '../../LayoutWidthContainer' +import Link from '../../Link' import CommunityBlock from '../Block' import CommunitySection from '../Section' +import { logEvent } from '../../../utils/front/ga' -import data from '../data' - -import { Button, Item, Items, Wrapper } from '../styles' +import data from '../data.json' +import sharedStyles from '../styles.module.css' const { description, mobileDescription, title } = data.section.contribute -const logPR = () => logEvent('community', 'contribute-pr') -const logBlogpost = () => logEvent('community', 'contribute-blogpost') -const logTalk = () => logEvent('community', 'contribute-talk') -const logAmbassador = () => logEvent('community', 'contribute-ambassador') +const logPR = (): void => logEvent('community', 'contribute-pr') +const logBlogpost = (): void => logEvent('community', 'contribute-blogpost') +const logTalk = (): void => logEvent('community', 'contribute-talk') +const logAmbassador = (): void => logEvent('community', 'contribute-ambassador') -export default function CommunityContribute({ theme }) { +const Contribute: React.SFC<{ theme: ICommunitySectionTheme }> = ({ + theme +}) => { return ( - + - - +
+
Go to Github - + } > Let's build something great together. Become a DVC contributor!. - - +
+
Let’s talk! - + } > We're always interested in guest writers for our blog. If you have something to share, please reach out! - - +
+
Let’s talk! - + } > We support speakers all over the world and help with preparation, speaker training and expenses. - - +
+
Let’s talk! - + } > Get perks and benefits for contributing to the code base, writing blog posts, or hosting meetups. - - +
+
-
+ ) } -CommunityContribute.propTypes = { - theme: PropTypes.shape({ - backgroundColor: PropTypes.string, - color: PropTypes.string - }) -} +export default Contribute diff --git a/src/components/Community/Events/index.js b/src/components/Community/Events/index.js deleted file mode 100644 index 5e3396a7a7..0000000000 --- a/src/components/Community/Events/index.js +++ /dev/null @@ -1,121 +0,0 @@ -import React, { useCallback } from 'react' -import PropTypes from 'prop-types' -import format from 'date-fns/format' -import fill from 'lodash.fill' - -import { logEvent } from '../../../utils/ga' - -import CommunityBlock from '../Block' -import CommunitySection from '../Section' -import Link from '../../Link' - -import data from '../data' - -import { Button, Item, Items, Line, Link as LinkSC, Wrapper } from '../styles' - -import { Image, ImageWrapper, Meta } from './styles' - -const { description, mobileDescription, title } = data.section.events -const { events } = data - -const modifiedEvents = events.length > 3 ? events.slice(0, 3) : events -const eventPlaceholders = fill(new Array(3 - modifiedEvents.length), Item) - -function CommunityEvent({ - theme, - city, - date, - description, - pictureUrl, - title, - url -}) { - const logEventClick = useCallback( - () => logEvent('community', 'event', title), - [title] - ) - - return ( - - - Event Info - - } - > - - - - - - {title} - - - {description} - - {city}, {format(new Date(date), 'MMMM d')} - - - - - ) -} - -CommunityEvent.propTypes = { - theme: PropTypes.object, - city: PropTypes.string, - date: PropTypes.string, - description: PropTypes.string, - pictureUrl: PropTypes.string, - title: PropTypes.string, - url: PropTypes.string -} - -export default function CommunityEvents({ theme }) { - if (!events.length) return '' - - return ( - - - - {modifiedEvents.map(event => ( - - ))} - {eventPlaceholders.map((Component, key) => ( - - ))} - - - - ) -} - -CommunityEvents.propTypes = { - theme: PropTypes.shape({ - backgroundColor: PropTypes.string, - color: PropTypes.string - }) -} diff --git a/src/components/Community/Events/index.tsx b/src/components/Community/Events/index.tsx new file mode 100644 index 0000000000..d74a1b669c --- /dev/null +++ b/src/components/Community/Events/index.tsx @@ -0,0 +1,126 @@ +import React, { useCallback } from 'react' +import cn from 'classnames' +import format from 'date-fns/format' + +import { ICommunitySectionTheme } from '../' +import LayoutWidthContainer from '../../LayoutWidthContainer' +import Link from '../../Link' +import Block from '../Block' +import Section from '../Section' +import { logEvent } from '../../../utils/front/ga' + +import data from '../data.json' +import sharedStyles from '../styles.module.css' +import styles from './styles.module.css' + +interface IEvent { + theme: ICommunitySectionTheme + city: string + date: string + description: string + pictureUrl?: string + title: string + url: string +} + +const { description, mobileDescription, title } = data.section.events +const { events } = data +const eventsItems = ((): Array => { + const items: Array = events.slice(0, 3) as Array + const itemLength = items.length + + for (let i = itemLength; i < 3; i++) { + items.push(null) + } + + return items +})() + +const Event: React.SFC = ({ + theme, + city, + date, + description, + pictureUrl, + title, + url +}) => { + const logEventClick = useCallback( + () => logEvent('community', 'event', title), + [title] + ) + + return ( + + Event Info + + } + > + + + + + + {title} + +
+
{description}
+
+ {city}, {format(new Date(date), 'MMMM d')} +
+
+
+ ) +} + +const Events: React.SFC<{ theme: ICommunitySectionTheme }> = ({ theme }) => { + if (!events.length) { + return null + } + + return ( + +
+
+ {eventsItems.map((event, key) => ( +
+ {event && } +
+ ))} +
+
+
+ ) +} + +export default Events diff --git a/src/components/Community/Events/styles.js b/src/components/Community/Events/styles.js deleted file mode 100644 index bea64fab3a..0000000000 --- a/src/components/Community/Events/styles.js +++ /dev/null @@ -1,17 +0,0 @@ -import styled from 'styled-components' - -import { Meta as ParentMeta } from '../styles' - -export const Image = styled.img` - width: 100%; - border-radius: 20px 20px 0 0; -` - -export const ImageWrapper = styled.a` - display: block; - margin: -10px -20px 0; -` - -export const Meta = styled(ParentMeta)` - margin-top: 10px; -` diff --git a/src/components/Community/Events/styles.module.css b/src/components/Community/Events/styles.module.css new file mode 100644 index 0000000000..4226e85a24 --- /dev/null +++ b/src/components/Community/Events/styles.module.css @@ -0,0 +1,13 @@ +.meta { + margin-top: 10px; +} + +.image { + width: 100%; + border-radius: 20px 20px 0 0; +} + +.imageWrapper { + display: block; + margin: -10px -20px 0; +} diff --git a/src/components/Community/Hero/index.js b/src/components/Community/Hero/index.js deleted file mode 100644 index f9a011b693..0000000000 --- a/src/components/Community/Hero/index.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react' - -import Link from '../../Link' - -import { logEvent } from '../../../utils/ga' - -import { OnlyDesktop, OnlyMobile } from '../../../styles' -import { Link as LinkSC, Picture, Wrapper } from './styles' - -import data from '../data.json' - -const logHero = () => logEvent('community', 'hero') - -export default function CommunityHero() { - if (!data.hero) return '' - - return ( - - - - - - - - - - - ) -} diff --git a/src/components/Community/Hero/index.tsx b/src/components/Community/Hero/index.tsx new file mode 100644 index 0000000000..8503442a7a --- /dev/null +++ b/src/components/Community/Hero/index.tsx @@ -0,0 +1,45 @@ +import React from 'react' + +import LayoutWidthContainer from '../../LayoutWidthContainer' +import ShowOnly from '../../ShowOnly' +import Link from '../../Link' +import { logEvent } from '../../../utils/front/ga' + +import data from '../data.json' +import styles from './styles.module.css' + +const logHero = (): void => logEvent('community', 'hero') + +const Hero: React.SFC = () => { + if (!data.hero) { + return null + } + + return ( + + + + + + + + + + + ) +} + +export default Hero diff --git a/src/components/Community/Hero/styles.js b/src/components/Community/Hero/styles.js deleted file mode 100644 index 3f007e8291..0000000000 --- a/src/components/Community/Hero/styles.js +++ /dev/null @@ -1,23 +0,0 @@ -import styled from 'styled-components' -import { media } from '../../../styles' - -export const Wrapper = styled.div` - margin: 0 auto; - padding: 40px 0 20px; - max-width: 1000px; - - ${media.tablet` - padding: 0 0 10px; - `} -` - -export const Link = styled.a` - &:hover { - opacity: 0.7; - } -` - -export const Picture = styled.img` - max-width: 1000px; - width: 100%; -` diff --git a/src/components/Community/Hero/styles.module.css b/src/components/Community/Hero/styles.module.css new file mode 100644 index 0000000000..dafea88e10 --- /dev/null +++ b/src/components/Community/Hero/styles.module.css @@ -0,0 +1,16 @@ +.container { + padding: 40px 0 20px; +} + +.link { + display: block; + + &:hover { + opacity: 0.7; + } +} + +.picture { + max-width: 100%; + width: 100%; +} diff --git a/src/components/Community/Learn/index.js b/src/components/Community/Learn/index.js deleted file mode 100644 index 308c49ad6c..0000000000 --- a/src/components/Community/Learn/index.js +++ /dev/null @@ -1,264 +0,0 @@ -import React, { useCallback } from 'react' -import PropTypes from 'prop-types' -import format from 'date-fns/format' - -import Link from '../../Link' - -import { logEvent } from '../../../utils/ga' -import { getFirstPage } from '../../../utils/sidebar' - -import CommunityBlock from '../Block' -import CommunitySection from '../Section' - -import { useCommentsCount } from '../../../utils/api' -import getPosts from '../../../queries/posts' -import { pluralizeComments } from '../../../utils/i18n' - -import { - Button, - Comments, - HeaderLink, - ImageLine, - Item, - Items, - Line, - Link as LinkSC, - Meta, - NbspWrapper, - TextWrapper, - Wrapper -} from '../styles' - -import { Image } from './styles' - -import data from '../data' - -const docsPage = getFirstPage() -const { description, mobileDescription, title } = data.section.learn -const { documentation, userContent } = data - -const logPostAll = () => logEvent('community', 'blog', 'all') -const logDocumentationAll = () => logEvent('community', 'documentation', 'all') - -function CommunityBlogPost({ - url, - title, - date, - color, - commentsUrl, - pictureUrl -}) { - const logPost = useCallback(() => logEvent('community', 'blog', title), [ - title - ]) - const { error, ready, result } = useCommentsCount(commentsUrl) - - return ( - - {pictureUrl && ( - - - - )} - - - {title} - - - {ready && !error && ( - <> - - {pluralizeComments(result)} - - {' • '} - - )} - {format(new Date(date), 'MMM, d')} - - - - ) -} - -CommunityBlogPost.propTypes = { - color: PropTypes.string, - commentsUrl: PropTypes.string, - pictureUrl: PropTypes.string, - date: PropTypes.string, - title: PropTypes.string, - url: PropTypes.string -} - -function CommunityUserContent({ url, title, author, date, color, pictureUrl }) { - const logUserContent = useCallback( - () => logEvent('community', 'user-content', title), - [title] - ) - - return ( - - {pictureUrl && ( - - - - )} - - - {title} - - - {author} •{' '} - {format(new Date(date), 'MMM, d')} - - - - ) -} - -CommunityUserContent.propTypes = { - author: PropTypes.string, - color: PropTypes.string, - date: PropTypes.string, - pictureUrl: PropTypes.string, - title: PropTypes.string, - url: PropTypes.string -} - -function CommunityDocumentation({ url, title, description, color }) { - const logDocumentation = useCallback( - () => logEvent('community', 'documentation', title), - [title] - ) - - return ( - - - {title} - - {description} - - ) -} - -CommunityDocumentation.propTypes = { - color: PropTypes.string, - description: PropTypes.string, - title: PropTypes.string, - url: PropTypes.string -} - -export default function CommunityLearn({ theme }) { - const posts = getPosts() - - return ( - - - - - - Documentation - - } - action={ - - See all docs - - } - > - {documentation.map(documentation => ( - - ))} - - - - - DVC Blog - - } - action={ - posts && ( - - ) - } - > - {posts.map(post => ( - - ))} - - - - - {userContent.map(userContent => ( - - ))} - - - - - - ) -} - -CommunityLearn.propTypes = { - theme: PropTypes.shape({ - backgroundColor: PropTypes.string, - color: PropTypes.string - }) -} diff --git a/src/components/Community/Learn/index.tsx b/src/components/Community/Learn/index.tsx new file mode 100644 index 0000000000..0c81986398 --- /dev/null +++ b/src/components/Community/Learn/index.tsx @@ -0,0 +1,275 @@ +import React, { useCallback } from 'react' +import cn from 'classnames' +import format from 'date-fns/format' + +import { ICommunitySectionTheme } from '../' +import LayoutWidthContainer from '../../LayoutWidthContainer' +import Link from '../../Link' +import Block from '../Block' +import Section from '../Section' + +import { logEvent } from '../../../utils/front/ga' +import { getFirstPage } from '../../../utils/shared/sidebar' +import { useCommentsCount } from '../../../utils/front/api' +import getPosts from '../../../queries/posts' +import { pluralizeComments } from '../../../utils/front/i18n' + +import data from '../data.json' +import sharedStyles from '../styles.module.css' +import styles from './styles.module.css' + +const docsPage = getFirstPage() +const { description, mobileDescription, title } = data.section.learn +const { documentation, userContent } = data + +const logPostAll = (): void => logEvent('community', 'blog', 'all') +const logDocumentationAll = (): void => + logEvent('community', 'documentation', 'all') + +interface ICommunityBlogPost { + color: string + commentsUrl?: string + pictureUrl: string | null + date: string + title: string + url: string +} + +const BlogPost: React.SFC = ({ + url, + title, + date, + color, + commentsUrl, + pictureUrl +}) => { + if (!commentsUrl) { + return null + } + + const logPost = useCallback(() => logEvent('community', 'blog', title), [ + title + ]) + + const { error, ready, result } = useCommentsCount(commentsUrl) + + return ( +
+ {pictureUrl && ( + + + + )} +
+ + {title} + +
+ {ready && !error && ( + <> + + {pluralizeComments(result || 0)} + + {' • '} + + )} + + {format(new Date(date), 'MMM, d')} + +
+
+
+ ) +} + +interface ICommunityUserContentProps { + author: string + color: string + date: string + pictureUrl: string + title: string + url: string +} + +const UserContent: React.SFC = ({ + url, + title, + author, + date, + color, + pictureUrl +}) => { + const logUserContent = useCallback( + () => logEvent('community', 'user-content', title), + [title] + ) + + return ( +
+ {pictureUrl && ( + + + + )} +
+ + {title} + +
+ {author} •{' '} + + {format(new Date(date), 'MMM, d')} + +
+
+
+ ) +} + +interface ICommunityDocumentationProps { + color: string + description: string + title: string + url: string +} + +const Documentation: React.SFC = ({ + url, + title, + description, + color +}) => { + const logDocumentation = useCallback( + () => logEvent('community', 'documentation', title), + [title] + ) + + return ( +
+ + {title} + +
{description}
+
+ ) +} + +const Learn: React.SFC<{ theme: ICommunitySectionTheme }> = ({ theme }) => { + const posts = getPosts() + + return ( + +
+
+
+ + Documentation + + } + action={ + + See all docs + + } + > + {documentation.map(documentation => ( + + ))} + +
+
+ + DVC Blog + + } + action={ + posts && ( + + See all Posts + + ) + } + > + {posts.map(post => ( + + ))} + +
+
+ + {userContent.map(userContent => ( + + ))} + +
+
+
+
+ ) +} + +export default Learn diff --git a/src/components/Community/Learn/styles.js b/src/components/Community/Learn/styles.module.css similarity index 55% rename from src/components/Community/Learn/styles.js rename to src/components/Community/Learn/styles.module.css index 6479afc7fd..7f2cf87340 100644 --- a/src/components/Community/Learn/styles.js +++ b/src/components/Community/Learn/styles.module.css @@ -1,9 +1,7 @@ -import styled from 'styled-components' - -export const Image = styled.img` +.image { width: 80px; height: 80px; border-radius: 5px; float: left; margin: 5px 10px 0 0; -` +} diff --git a/src/components/Community/Meet/index.js b/src/components/Community/Meet/index.js deleted file mode 100644 index 3384f57360..0000000000 --- a/src/components/Community/Meet/index.js +++ /dev/null @@ -1,255 +0,0 @@ -import React, { useCallback } from 'react' -import PropTypes from 'prop-types' -import formatDistanceToNow from 'date-fns/formatDistanceToNow' - -import CommunityBlock from '../Block' -import CommunitySection from '../Section' -import Link from '../../Link' - -import { pluralizeComments } from '../../../utils/i18n' -import { logEvent } from '../../../utils/ga' -import { useIssues, useTopics } from '../../../utils/api' - -import data from '../data' - -const { description, mobileDescription, title } = data.section.meet - -import { Stats, StatLabel, StatLine, StatValue } from './styles' - -import { - Button, - Comments, - HeaderLink, - Item, - Items, - Line, - Link as LinkSC, - Meta, - Placeholder, - Wrapper -} from '../styles' - -const logIssueAll = () => logEvent('community', 'issue', 'all') -const logTopicAll = () => logEvent('community', 'topic', 'all') -const logDiscord = () => logEvent('community', 'discord') - -function CommunityTopic({ url, title, date, comments, color }) { - const logTopic = useCallback(() => logEvent('community', 'forum', title), [ - title - ]) - - return ( - - - {title} - - - - {pluralizeComments(comments)} - {' '} - • last activity {formatDistanceToNow(new Date(date), 'MMM, d') + ' '} - ago - - - ) -} - -CommunityTopic.propTypes = { - url: PropTypes.string, - title: PropTypes.string, - date: PropTypes.string, - comments: PropTypes.number, - color: PropTypes.string -} - -function CommunityIssue({ url, title, date, comments, color }) { - const logIssue = useCallback(() => logEvent('community', 'issue', title), [ - title - ]) - - return ( - - - {title} - - - - {pluralizeComments(comments)} - - {' •'} opened {formatDistanceToNow(new Date(date), 'MMM, d')} ago - - - ) -} - -CommunityIssue.propTypes = { - url: PropTypes.string, - title: PropTypes.string, - date: PropTypes.string, - comments: PropTypes.number, - color: PropTypes.string -} - -export default function CommunityMeet({ theme }) { - const { erorr: issuesError, ready: issuesReady, result: issues } = useIssues() - const { erorr: topicsError, ready: topicsReady, result: topics } = useTopics() - - return ( - - - - - - Join the Dev Chat - - } - action={ - - Open Chat - - } - icon="/img/community/discord.svg" - > - - Need urgent help? Ask advice from experienced developers online - - - - {data.stats.users} - registered developers - - - {data.stats.messages} - messages posted over the past month - - - - - - - Ask a Question - - } - action={ - topics && ( - - Read All Topics - - ) - } - icon="/img/community/discourse.svg" - > - {!topicsReady && Loading...} - {topicsError && ( - Forum unavailable right now - )} - {topics && - topics.map(topic => ( - - ))} - - - - - Post an Issue - - } - action={ - issues && ( - - Read All Issues - - ) - } - icon="/img/community/github.svg" - > - {!issuesReady && Loading...} - {issuesError && ( - Github unavailable right now - )} - {issues && - issues.map(issue => ( - - ))} - - - - - - ) -} - -CommunityMeet.propTypes = { - theme: PropTypes.shape({ - backgroundColor: PropTypes.string, - color: PropTypes.string - }) -} diff --git a/src/components/Community/Meet/index.tsx b/src/components/Community/Meet/index.tsx new file mode 100644 index 0000000000..4c42e068ed --- /dev/null +++ b/src/components/Community/Meet/index.tsx @@ -0,0 +1,248 @@ +import React, { useCallback } from 'react' +import formatDistanceToNow from 'date-fns/formatDistanceToNow' + +import { ICommunitySectionTheme } from '../' +import LayoutWidthContainer from '../../LayoutWidthContainer' +import Block from '../Block' +import Section from '../Section' +import Link from '../../Link' +import { pluralizeComments } from '../../../utils/front/i18n' +import { logEvent } from '../../../utils/front/ga' +import { + useIssues, + useTopics, + IGithubIssue, + IDiscussTopic +} from '../../../utils/front/api' + +import data from '../data.json' +import sharedStyles from '../styles.module.css' +import styles from './styles.module.css' + +const { description, mobileDescription, title } = data.section.meet +const logIssueAll = (): void => logEvent('community', 'issue', 'all') +const logTopicAll = (): void => logEvent('community', 'topic', 'all') +const logDiscord = (): void => logEvent('community', 'discord') + +const Topic: React.SFC<{ color: string } & IDiscussTopic> = ({ + url, + title, + date, + comments, + color +}) => { + const logTopic = useCallback(() => logEvent('community', 'forum', title), [ + title + ]) + + return ( +
+ + {title} + +
+ + {pluralizeComments(comments)} + + {` • last activity ${formatDistanceToNow(new Date(date))} ago`} +
+
+ ) +} + +const Issue: React.SFC<{ color: string } & IGithubIssue> = ({ + url, + title, + date, + comments, + color +}) => { + const logIssue = useCallback(() => logEvent('community', 'issue', title), [ + title + ]) + + return ( +
+ + {title} + +
+ + {pluralizeComments(comments)} + + {` • opened ${formatDistanceToNow(new Date(date))} ago`} +
+
+ ) +} + +const Meet: React.SFC<{ theme: ICommunitySectionTheme }> = ({ theme }) => { + const { error: issuesError, ready: issuesReady, result: issues } = useIssues() + const { error: topicsError, ready: topicsReady, result: topics } = useTopics() + + return ( + +
+
+
+ + Join the Dev Chat + + } + action={ + + Open Chat + + } + icon="/img/community/discord.svg" + > +
+ Need urgent help? Ask advice from experienced developers online +
+
+
+
{data.stats.users}
+
registered developers
+
+
+
{data.stats.messages}
+
+ messages posted over the past month +
+
+
+
+
+
+ + Ask a Question + + } + action={ + topics && ( + + Read All Topics + + ) + } + icon="/img/community/discourse.svg" + > + {!topicsReady && ( +
Loading...
+ )} + {topicsError && ( +
+ Forum unavailable right now +
+ )} + {topics && + topics.map(topic => ( + + ))} +
+
+
+ + Post an Issue + + } + action={ + issues && ( + + Read All Issues + + ) + } + icon="/img/community/github.svg" + > + {!issuesReady && ( +
Loading...
+ )} + {issuesError && ( +
+ Github unavailable right now +
+ )} + {issues && + issues.map(issue => ( + + ))} +
+
+
+
+
+ ) +} + +export default Meet diff --git a/src/components/Community/Meet/styles.js b/src/components/Community/Meet/styles.module.css similarity index 54% rename from src/components/Community/Meet/styles.js rename to src/components/Community/Meet/styles.module.css index 37fbe5bf41..f611fd26fa 100644 --- a/src/components/Community/Meet/styles.js +++ b/src/components/Community/Meet/styles.module.css @@ -1,26 +1,23 @@ -import styled from 'styled-components' -import { media } from '../../../styles' - -export const Stats = styled.div` - ${media.tablet` +.stats { + @media (--sm-scr) { display: flex; flex-direction: row; margin-top: 15px; - `} -` + } +} -export const StatLabel = styled.div` +.statLabel { font-size: 16px; line-height: 20px; - color: #838d93; -` + color: var(--color-gray); +} -export const StatLine = styled.div` +.statLine { display: flex; align-items: center; margin-top: 25px; - ${media.tablet` + @media (--sm-scr) { flex-direction: column; flex-basis: 50%; align-items: flex-start; @@ -29,23 +26,22 @@ export const StatLine = styled.div` & + & { margin-left: 25px; } - `} -` + } +} -export const StatValue = styled.div` +.statValue { flex: 0 0 124px; margin-right: 20px; + font-family: var(--font-brandon); font-size: 40px; - font-family: BrandonGrotesque; + line-height: 50px; font-weight: 900; text-align: right; - line-height: 50px; - color: #24292e; + color: var(--color-black); - ${media.tablet` + @media (--sm-scr) { flex: initial; font-size: 30px; - font-family: BrandonGrotesque; line-height: 40px; - `} -` + } +} diff --git a/src/components/Community/Section/index.js b/src/components/Community/Section/index.js deleted file mode 100644 index c13a6ff357..0000000000 --- a/src/components/Community/Section/index.js +++ /dev/null @@ -1,83 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useCallback, useEffect, useState } from 'react' -import { useLocation } from '@reach/router' -import { Collapse } from 'react-collapse' -import { useWindowSize } from 'react-use' - -import { - DesktopDescription, - Header, - Icon, - MobileDescription, - Picture, - Title, - Wrapper -} from './styles' - -import { sizes } from '../../../styles' - -export default function CommunitySection({ - anchor, - background, - color, - contentVisible = false, - children, - description, - icon, - mobileDescription, - title -}) { - const [isTablet, setIsTablet] = useState(false) - const [isContentVisible, setIsContentVisible] = useState(contentVisible) - const toggleVisibility = useCallback( - () => setIsContentVisible(!isContentVisible), - [isContentVisible] - ) - - const { width } = useWindowSize() - const location = useLocation() - - useEffect(() => { - if (anchor && location.hash === `#${anchor}`) { - setIsContentVisible(true) - } - }, [location.hash]) - - useEffect(() => setIsTablet(width <= sizes.tablet), [width]) - - return ( - -
- -
- {title} - {description} - {mobileDescription} -
-
- {background && } - - {isTablet ? ( - {children} - ) : ( - children - )} -
- ) -} - -CommunitySection.propTypes = { - anchor: PropTypes.string, - background: PropTypes.string, - color: PropTypes.string, - contentVisible: PropTypes.bool, - children: PropTypes.node, - description: PropTypes.string, - icon: PropTypes.string, - mobileDescription: PropTypes.string, - title: PropTypes.string -} diff --git a/src/components/Community/Section/index.tsx b/src/components/Community/Section/index.tsx new file mode 100644 index 0000000000..af75757b8c --- /dev/null +++ b/src/components/Community/Section/index.tsx @@ -0,0 +1,100 @@ +import React, { useCallback, useEffect, useState } from 'react' +import cn from 'classnames' +import { useLocation } from '@reach/router' +import { Collapse } from 'react-collapse' +import { useWindowSize } from 'react-use' + +import { isTriggeredFromKB } from '../../../utils/front/keyboard' +import { screens } from '../../../../config/postcss/media' + +import styles from './styles.module.css' + +interface ICommunitySection { + anchor: string + background?: string + color: string + contentVisible?: boolean + children: React.ReactNode + description: string + icon: string + mobileDescription: string + title: string +} + +const Section: React.SFC = ({ + anchor, + background, + color, + contentVisible = false, + children, + description, + icon, + mobileDescription, + title +}) => { + const [isTablet, setIsTablet] = useState(false) + const [isContentVisible, setIsContentVisible] = useState(contentVisible) + const toggleVisibility = useCallback( + () => setIsContentVisible(!isContentVisible), + [isContentVisible] + ) + const toggleFromKeyboard = useCallback( + (e: React.KeyboardEvent) => { + if (isTriggeredFromKB(e)) { + toggleVisibility() + } + }, + [toggleVisibility] + ) + const { width } = useWindowSize() + const location = useLocation() + + useEffect(() => { + if (anchor && location.hash === `#${anchor}`) { + setIsContentVisible(true) + } + }, [location.hash]) + + useEffect(() => setIsTablet(width <= screens.tablet), [width]) + + return ( +
+
+ +
+
+ {title} +
+
{description}
+
+ {mobileDescription} +
+
+
+ {background && } + + {isTablet ? ( + {children} + ) : ( + children + )} +
+ ) +} + +export default Section diff --git a/src/components/Community/Section/styles.js b/src/components/Community/Section/styles.js deleted file mode 100644 index 2c4b416985..0000000000 --- a/src/components/Community/Section/styles.js +++ /dev/null @@ -1,106 +0,0 @@ -import styled from 'styled-components' -import { media } from '../../../styles' - -export const Wrapper = styled.div` - position: relative; - margin: 50px -50px; - padding: ${({ hasBg }) => (hasBg ? '0 50px 260px' : '0 50px')}; - - ${media.desktop` - padding: ${({ hasBg }) => (hasBg ? '0 65px 260px' : '0 65px')}; - `} - - ${media.tablet` - margin: 0; - padding: 20px 0; - `} - - .ReactCollapse--collapse { - transition: height 500ms; - } -` - -export const Header = styled.div` - display: flex; - color: ${({ color }) => color}; -` - -export const Title = styled.div` - font-size: 40px; - font-family: BrandonGrotesque; - font-weight: 500; - line-height: 60px; - - ${media.tablet` - font-size: 30px; - line-height: 35px; - cursor: pointer; - - &::after { - content: ''; - - position: relative; - display: inline-block; - width: 0; - height: 0; - margin-left: 10px; - border-left: 7px solid transparent; - border-right: 7px solid transparent; - border-top: 12px solid currentColor; - transition: transform 200ms; - - ${({ isContentVisible }) => - isContentVisible && - `transform: rotate(-180deg) - `} - - } - `} -` - -export const Icon = styled.img` - width: 50px; - height: 50px; - margin: 5px 10px 0 0; -` - -const Description = styled.div` - max-width: 600px; - font-size: 16px; - line-height: 24px; - color: #838d93; - - ${media.tablet` - display: none; - `}; -` - -export const DesktopDescription = styled(Description)` - max-width: 600px; - - ${media.tablet` - display: none; - `}; -` - -export const MobileDescription = styled(Description)` - display: none; - - ${media.tablet` - display: block; - `}; -` - -export const Picture = styled.img` - z-index: -1; - position: absolute; - bottom: 0; - left: 50%; - width: 1100px; - height: 450px; - margin-left: -550px; - - ${media.tablet` - display: none; - `} -` diff --git a/src/components/Community/Section/styles.module.css b/src/components/Community/Section/styles.module.css new file mode 100644 index 0000000000..bbecaae494 --- /dev/null +++ b/src/components/Community/Section/styles.module.css @@ -0,0 +1,107 @@ +.container { + position: relative; + margin: 50px -50px; + padding: 0 50px; + + &.hasBg { + padding: 0 50px 260px; + } + + @media (--md-scr) { + padding: 0 40px; + margin-left: -15px; + margin-right: -15px; + overflow: hidden; + + &.hasBg { + padding: 0 40px 260px; + } + } + + @media (--sm-scr) { + margin: 0; + padding: 20px 0; + + &.hasBg { + margin: 0; + padding: 20px 0; + } + } + + :global(.ReactCollapse--collapse) { + transition: height 500ms; + } +} + +.header { + display: flex; +} + +.title { + font-family: var(--font-brandon); + font-size: 40px; + font-weight: 500; + line-height: 60px; + + @media (--sm-scr) { + font-size: 30px; + line-height: 35px; + cursor: pointer; + + &::after { + content: ''; + position: relative; + display: inline-block; + width: 0; + height: 0; + margin-left: 10px; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-top: 12px solid currentColor; + transition: transform 200ms; + } + + &.isContentVisible::after { + transform: rotate(-180deg); + } + } +} + +.icon { + width: 50px; + height: 50px; + margin: 5px 10px 0 0; +} + +.description { + max-width: 600px; + font-size: 16px; + line-height: 24px; + color: var(--color-gray); + + @media (--sm-scr) { + display: none; + } + + &.mobile { + display: none; + + @media (--sm-scr) { + display: block; + } + } +} + +.picture { + position: absolute; + z-index: -1; + bottom: 0; + left: 50%; + width: 1100px; + height: 450px; + margin-left: -550px; + + @media (--sm-scr) { + display: none; + } +} diff --git a/src/components/Community/index.js b/src/components/Community/index.js deleted file mode 100644 index e711d01f63..0000000000 --- a/src/components/Community/index.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react' - -import Subscribe from '../Subscribe' - -import CommunityContribute from './Contribute' -import CommunityEvents from './Events' -import CommunityHero from './Hero' -import CommunityLearn from './Learn' -import CommunityMeet from './Meet' - -import { PageWrapper } from './styles' - -const themes = { - green: { backgroundColor: '#C2E6EE', color: '#13ADC7' }, - orange: { backgroundColor: '#EFD8D1', color: '#F46737' }, - purple: { backgroundColor: '#DCD6F1', color: '#955DD6' } -} - -export default function Community() { - return ( - <> - - - - - - - - - - ) -} diff --git a/src/components/Community/index.tsx b/src/components/Community/index.tsx new file mode 100644 index 0000000000..788efc7159 --- /dev/null +++ b/src/components/Community/index.tsx @@ -0,0 +1,38 @@ +import React from 'react' + +import PageContent from '../PageContent' + +import SubscribeSection from '../SubscribeSection' +import Hero from './Hero' +import Meet from './Meet' +import Contribute from './Contribute' +import Learn from './Learn' +import Events from './Events' + +import styles from './styles.module.css' + +const themes = { + green: { backgroundColor: '#C2E6EE', color: '#13ADC7' }, + orange: { backgroundColor: '#EFD8D1', color: '#F46737' }, + purple: { backgroundColor: '#DCD6F1', color: '#955DD6' } +} + +export interface ICommunitySectionTheme { + backgroundColor: string + color: string +} + +const Community: React.SFC = () => ( + <> + + + + + + + + + +) + +export default Community diff --git a/src/components/Community/styles.js b/src/components/Community/styles.js deleted file mode 100644 index 8409366018..0000000000 --- a/src/components/Community/styles.js +++ /dev/null @@ -1,143 +0,0 @@ -import styled from 'styled-components' -import { media } from '../../styles' - -export const Comments = styled.a` - text-decoration: none; - color: inherit; - - &:hover { - opacity: 0.7; - } -` - -export const Item = styled.div` - display: flex; - flex-grow: 1; - flex-basis: 0; - align-items: stretch; - - & + & { - margin-left: 30px; - - ${media.desktop` - margin-left: 15px; - `} - - ${media.tablet` - display: block; - margin: 15px 0 0 0; - `} - } -` - -export const Items = styled.div` - display: flex; - align-items: stretch; - padding-top: 40px; - - ${media.phablet` - padding-top: 20px; - `} - - ${media.tablet` - display: block; - `} -` - -export const Line = styled.div` - overflow: hidden; - - & + & { - margin-top: 20px; - } -` -export const ImageLine = styled(Line)` - display: flex; -` - -export const Link = styled.a` - font-size: ${({ large }) => (large ? '24px' : '16px')}; - font-family: BrandonGrotesque; - font-weight: 600; - line-height: ${({ large }) => (large ? '34px' : '18px')}; - text-decoration: none; - word-break: break-word; - overflow-wrap: anywhere; - color: ${({ color }) => color}; - - &:hover { - opacity: 0.7; - } -` - -export const Meta = styled.div` - line-height: 20px; -` - -export const Placeholder = styled.div` - display: flex; - align-items: center; - justify-content: center; - height: 100%; -` - -export const Wrapper = styled.div` - max-width: 1000px; - margin: 0 auto; - - ${media.tablet` - margin: 0 15px; - - & + & { - border-top: 1px solid #e6e8e9; - } - `} -` - -export const TextWrapper = styled.div`` - -export const NbspWrapper = styled.span` - white-space: nowrap; -` - -export const HeaderLink = styled.a` - font-family: BrandonGrotesque; - font-size: 24px; - font-weight: 500; - line-height: 34px; - color: #24292e; - text-decoration: none; - - &:hover { - opacity: 0.7; - } -` - -export const PageWrapper = styled.div` - ${media.tablet` - padding-bottom: 30px; - `} - - ${media.phablet` - padding-bottom: 0; - `} -` - -export const Button = styled.a` - display: block; - height: 38px; - border-radius: 4px; - font-size: 16px; - font-family: BrandonGrotesque; - font-weight: 500; - line-height: 38px; - text-decoration: none; - text-align: center; - color: ${({ theme: { color } }) => (color ? color : '#999')}; - background-color: ${({ theme: { backgroundColor } }) => - backgroundColor ? backgroundColor : '#ddd'}; - - &:hover { - opacity: 0.7; - } -` diff --git a/src/components/Community/styles.module.css b/src/components/Community/styles.module.css new file mode 100644 index 0000000000..d7a40ba3d1 --- /dev/null +++ b/src/components/Community/styles.module.css @@ -0,0 +1,135 @@ +.content { + @media (--xs-scr) { + padding-bottom: 0; + } +} + +.commentsLink { + text-decoration: none; + color: inherit; + + &:hover { + opacity: 0.7; + } +} + +.items { + display: flex; + align-items: stretch; + padding-top: 40px; + + @media (--sm-scr) { + display: block; + } + + @media (--xs-scr) { + padding-top: 20px; + } +} + +.item { + display: flex; + flex-grow: 1; + flex-basis: 0; + align-items: stretch; + + & + & { + margin-left: 30px; + + @media (--md-scr) { + margin-left: 15px; + } + + @media (--sm-scr) { + display: block; + margin: 15px 0 0 0; + } + } +} + +.line { + overflow: hidden; + + & + & { + margin-top: 20px; + } + + &.image { + display: flex; + } +} + +.link { + font-family: var(--font-brandon); + font-size: 16px; + line-height: 18px; + font-weight: 600; + text-decoration: none; + word-break: break-word; + overflow-wrap: anywhere; + + &.large { + font-size: 24px; + line-height: 34px; + } + + &:hover { + opacity: 0.7; + } +} + +.meta { + line-height: 20px; +} + +.placeholder { + display: flex; + align-items: center; + justify-content: center; + height: 100%; +} + +.wrapper { + @media (--sm-scr) { + padding: 0 15px; + + & + & { + border-top: 1px solid #e6e8e9; + } + } +} + +.nbsp { + white-space: nowrap; +} + +.headerLink { + font-family: var(--font-brandon); + font-size: 24px; + line-height: 34px; + font-weight: 500; + color: var(--color-black); + text-decoration: none; + + &:hover { + opacity: 0.7; + } +} + +.button { + display: block; + height: 38px; + border-radius: 4px; + font-family: var(--font-brandon); + font-size: 16px; + line-height: 38px; + font-weight: 500; + text-decoration: none; + text-align: center; + color: #999; + background-color: #ddd; + + &:hover { + opacity: 0.7; + } +} diff --git a/src/components/Diagram/index.js b/src/components/Diagram/index.js deleted file mode 100644 index a59448f958..0000000000 --- a/src/components/Diagram/index.js +++ /dev/null @@ -1,165 +0,0 @@ -/* eslint jsx-a11y/anchor-is-valid: off */ - -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import { Element } from 'react-scroll' -import Slider from 'react-slick' - -import 'slick-carousel/slick/slick.css' -import 'slick-carousel/slick/slick-theme.css' - -import Link from '../Link' - -import { OnlyDesktop, OnlyMobile } from '../../styles' - -import { - Abstract, - Caption, - Column, - Columns, - Container, - Description, - Diagram, - Graphic, - LearnMoreArea, - Slide, - SliderDots, - SliderWrapper, - Title -} from './styles' - -const LearnMore = ({ href }) => ( - - - Learn more - - - -) - -LearnMore.propTypes = { - href: PropTypes.string.isRequired -} - -const ColumnOne = () => ( - - ML project version control - -

- Version control machine learning models, data sets and intermediate - files. DVC connects them with code, and uses Amazon S3, Microsoft Azure - Blob Storage, Google Drive, Google Cloud Storage, Aliyun OSS, SSH/SFTP, - HDFS, HTTP, network-attached storage, or disc to store file contents. -

-

- Full code and data provenance help track the complete evolution of every - ML model. This guarantees reproducibility and makes it easy to switch - back and forth between experiments. -

-
- -
-) - -const ColumnTwo = () => ( - - ML experiment management - -

- Harness the full power of Git branches to try different ideas instead of - sloppy file suffixes and comments in code. Use automatic metric-tracking - to navigate instead of paper and pencil. -

-

- DVC was designed to keep branching as simple and fast as in Git — no - matter the data file size. Along with first-class citizen metrics and ML - pipelines, it means that a project has cleaner structure. It's easy - to compare ideas and pick the best. Iterations become faster with - intermediate artifact caching. -

-
- -
-) - -const ColumnThree = () => ( - - Deployment & Collaboration - -

- Instead of ad-hoc scripts, use push/pull commands to move consistent - bundles of ML models, data, and code into production, remote machines, - or a colleague's computer. -

-

- DVC introduces lightweight pipelines as a first-class citizen mechanism - in Git. They are language-agnostic and connect multiple steps into a - DAG. These pipelines are used to remove friction from getting code into - production. -

-
- -
-) - -export default class DiagramSection extends Component { - render() { - const imagesSliderProps = { - slidesToShow: 1, - slidesToScroll: 1, - initialSlide: 1, - infinite: true, - speed: 600, - buttons: true, - dots: true, - appendDots: dots => {dots} - } - - return ( - - - - DVC tracks ML models and data sets - - DVC is built to make ML models shareable and reproducible. It is - designed to handle large files, data sets, machine learning models, - and metrics as well as code. - - - - - - - - - - - - - - - - - - ML project version control - - - - ML experiment management - - - - Deployment & Collaboration - - - - - - - - ) - } -} diff --git a/src/components/Diagram/styles.js b/src/components/Diagram/styles.js deleted file mode 100644 index 34ab734608..0000000000 --- a/src/components/Diagram/styles.js +++ /dev/null @@ -1,187 +0,0 @@ -import styled from 'styled-components' - -import { column, columns, container, media } from '../../styles' - -export const Diagram = styled.section` - padding-top: 80px; - padding-bottom: 91px; -` - -export const Container = styled.div` - ${container}; -` - -export const Title = styled.div` - font-family: BrandonGrotesque; - max-width: 550px; - min-height: 44px; - font-size: 30px; - font-weight: 500; - text-align: center; - color: #40364d; - margin: 0px auto; -` - -export const Abstract = styled.div` - margin: 0px auto; - padding-top: 10px; - max-width: 590px; - min-height: 50px; - font-size: 16px; - text-align: center; - color: #5f6c72; - line-height: 1.5; -` - -export const Graphic = styled.section` - width: 100%; - margin-top: 49px; - - img { - width: 100%; - max-width: 900px; - max-height: 445px; - } - - ${media.phablet` - overflow-x: scroll; - overflow-y: hidden; - `}; - - @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) { - overflow-x: scroll; - overflow-y: hidden; - } -` - -export const Columns = styled.div` - ${columns}; - margin-top: 10px; - flex-direction: row; - justify-content: center; - align-items: flex-start; - ${media.tablet`flex-direction: column;`}; -` - -export const Column = styled.div` - ${column}; - max-width: 33.3%; - display: block; - margin-top: 49px; - padding: 0 10px; - box-sizing: border-box; - - ${media.tablet` - margin-right: 0px; - flex-basis: auto; - max-width: 100%; - `}; - - ${media.phablet` - margin-top: 20px; - flex-basis: auto; - max-width: 100%; - `}; -` - -export const Caption = styled.h3` - font-family: BrandonGrotesque; - margin-bottom: 12px; - font-size: 20px; - font-weight: 500; - color: ${props => props.text}; -` - -export const Description = styled.div` - max-width: ${props => (props.fullWidth ? '100%' : '311px')}; - font-size: 16px; - color: #5f6c72; - - p { - margin-bottom: 24px; - - ${media.tablet` - margin-bottom: 12px; - `}; - } -` - -export const LearnMoreArea = styled.div` - font-family: BrandonGrotesque; - line-height: 28px; - font-size: 20px; - font-weight: 500; - color: #945dd6; - - img { - margin-left: 19px; - margin-top: 3px; - } - - a { - display: flex; - align-items: center; - text-decoration: none; - color: #945dd6; - } - - a:hover { - color: #745cb7; - } - - a:visited { - color: #945dd6; - } - - a:visited:hover { - color: #745cb7; - } -` - -export const SliderWrapper = styled.div` - .slick-next, - .slick-prev { - height: 30px; - width: 30px; - z-index: 3; - } - - .slick-next { - right: -25px; - } - - .slick-prev { - left: -25px; - } - - .slick-next:before, - .slick-prev:before { - font-size: 30px; - line-height: 1; - opacity: 0.35; - color: #40364d; - } - - img { - pointer-events: none; - } -` - -export const Slide = styled.div` - width: 100%; - img { - padding-top: 20px; - padding-bottom: 20px; - width: 100%; - max-width: 380px; - margin: 0 auto; - } -` - -export const SliderDots = styled.ul` - margin-bottom: -20px; - - li button::before { - font-size: 8px; - } -` diff --git a/src/components/SearchForm/index.js b/src/components/Documentation/Layout/SearchForm/index.tsx similarity index 63% rename from src/components/SearchForm/index.js rename to src/components/Documentation/Layout/SearchForm/index.tsx index a97911c835..3782598049 100644 --- a/src/components/SearchForm/index.js +++ b/src/components/Documentation/Layout/SearchForm/index.tsx @@ -1,19 +1,20 @@ import React, { useEffect, useState } from 'react' import Promise from 'promise-polyfill' -import { loadResource } from '../../utils/resources' +import { loadResource } from '../../../../utils/front/resources' -import { SearchArea, Input, Wrapper } from './styles' +import styles from './styles.module.css' -export default function SearchForm(props) { +declare global { + // eslint-disable-next-line @typescript-eslint/interface-name-prefix + interface Window { + docsearch?: (opts: object) => void + } +} + +const SearchForm: React.SFC = props => { const [isLoaded, setLoaded] = useState(false) useEffect(() => { - // When mailchimp loads it adds AMD support and docsearch define new AMD - // unnamed(!) modules instead of global variable - if (window.define) { - window.define.amd = false - } - Promise.all([ loadResource( 'https://cdn.jsdelivr.net/npm/docsearch.js@2.6.2/dist/cdn/docsearch.min.css' @@ -41,15 +42,18 @@ export default function SearchForm(props) { } return ( - - - +
+ - - +
+
) } + +export default SearchForm diff --git a/src/components/SearchForm/styles.js b/src/components/Documentation/Layout/SearchForm/styles.module.css similarity index 67% rename from src/components/SearchForm/styles.js rename to src/components/Documentation/Layout/SearchForm/styles.module.css index bc74136b77..bb0ea37041 100644 --- a/src/components/SearchForm/styles.js +++ b/src/components/Documentation/Layout/SearchForm/styles.module.css @@ -1,8 +1,4 @@ -import styled from 'styled-components' - -import { media } from '../../styles' - -export const SearchArea = styled.div` +.searchArea { box-sizing: border-box; height: 60px; padding-top: 10px; @@ -15,40 +11,43 @@ export const SearchArea = styled.div` ); z-index: 10; position: sticky; - top: var(--layout-header-height-scrolled); + top: var(--layout-header-height-collapsed); - ${media.phablet` + @media (--xs-scr) { position: relative; padding: 10px 20px 0; top: 0; - `}; + } - form { + input { height: 40px; } -` -export const Wrapper = styled.form` + :global(.algolia-autocomplete .ds-dropdown-menu) { + margin-top: -5px; + } +} + +.container { width: 100%; height: 100%; display: flex; -` +} -export const Input = styled.input` +.input { + box-sizing: border-box; display: flex; flex: 1; + width: 240px; height: 100%; - border-radius: 200px; - background-color: #ffffff; - border: solid 1px #dbe4ea; padding-left: 48px; padding-right: 24px; + border: solid 1px var(--color-lighter-blue); + border-radius: 200px; + background-color: #fff; background-image: url('/img/search.svg'); background-repeat: no-repeat; background-position: 15px center; font-size: 16px; font-weight: 500; - width: 240px; - box-sizing: border-box; - display: block; -` +} diff --git a/src/components/Documentation/Layout/SidebarMenu/index.js b/src/components/Documentation/Layout/SidebarMenu/index.js deleted file mode 100644 index f764aaa60c..0000000000 --- a/src/components/Documentation/Layout/SidebarMenu/index.js +++ /dev/null @@ -1,139 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react' -import { Collapse } from 'react-collapse' -import PerfectScrollbar from 'perfect-scrollbar' -import PropTypes from 'prop-types' -import includes from 'lodash.includes' - -import 'perfect-scrollbar/css/perfect-scrollbar.css' - -import DownloadButton from '../../../DownloadButton' -import Link from '../../../Link' - -import { - getParentsListFromPath, - getPathWithSoruce -} from '../../../../utils/sidebar' - -import { OnlyDesktop } from '../../../../styles' - -import { Menu, SectionLink, SectionLinks, Sections, SideFooter } from './styles' - -function SidebarMenuItem({ children, label, path, activePaths, onClick }) { - const isActive = activePaths && includes(activePaths, path) - const isRootParent = - activePaths && activePaths.length > 1 && activePaths[0] === path - - return ( - <> - - {label} - - {children && ( - - {children.map(item => ( - - ))} - - )} - - ) -} - -SidebarMenuItem.propTypes = { - children: PropTypes.arrayOf(PropTypes.object), - label: PropTypes.string.isRequired, - path: PropTypes.string.isRequired, - source: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]).isRequired, - onClick: PropTypes.func, - activePaths: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.string), - PropTypes.bool - ]).isRequired -} - -export default function SidebarMenu({ id, sidebar, currentPath, onClick }) { - const psRef = useRef() - const [isScrollHidden, setIsScrollHidden] = useState(false) - const activePaths = currentPath && getParentsListFromPath(currentPath) - - useEffect(() => { - if (!psRef.current) { - psRef.current = new PerfectScrollbar(`#${id}`, { - wheelPropagation: true - }) - } - - const node = document.getElementById(currentPath) - const parent = document.getElementById(id) - - setIsScrollHidden(true) - - const timeout = setTimeout(() => { - psRef.current.update() - - if (node && parent) { - const parentHeight = parent.clientHeight - const parentScroll = parent.scrollTop - const nodeOffset = node.offsetTop - const nodeHeight = node.clientHeight - const scrollOffset = nodeOffset - parentHeight + nodeHeight - - if ( - parentScroll > nodeOffset + nodeHeight || - parentScroll + parentHeight < nodeOffset - ) { - parent.scrollTop = scrollOffset - } - } - - setIsScrollHidden(false) - }, 400) - - return () => { - clearTimeout(timeout) - psRef.current.destroy() - psRef.current = null - } - }, [currentPath]) - - return ( - - - - {sidebar.map(item => ( - - ))} - - - - - - - - - ) -} - -SidebarMenu.propTypes = { - id: PropTypes.string.isRequired, - sidebar: PropTypes.arrayOf(PropTypes.object).isRequired, - currentPath: PropTypes.string, - onClick: PropTypes.func -} diff --git a/src/components/Documentation/Layout/SidebarMenu/index.tsx b/src/components/Documentation/Layout/SidebarMenu/index.tsx new file mode 100644 index 0000000000..c7b87ee97c --- /dev/null +++ b/src/components/Documentation/Layout/SidebarMenu/index.tsx @@ -0,0 +1,157 @@ +import React, { useEffect, useRef, useState } from 'react' +import { useLocation } from '@reach/router' +import cn from 'classnames' +import { Collapse } from 'react-collapse' +import PerfectScrollbar from 'perfect-scrollbar' +import includes from 'lodash.includes' + +import ShowOnly from '../../../ShowOnly' +import DownloadButton from '../../../DownloadButton' +import Link from '../../../Link' + +import { + structure, + getParentsListFromPath, + getPathWithSoruce +} from '../../../../utils/shared/sidebar' + +import 'perfect-scrollbar/css/perfect-scrollbar.css' +import styles from './styles.module.css' + +interface ISidebarMenuItemProps { + children?: Array<{ label: string; path: string; source: boolean | string }> + label: string + path: string + source: boolean | string + onClick: (e: React.MouseEvent) => void + activePaths?: Array +} + +const SidebarMenuItem: React.SFC = ({ + children, + label, + path, + activePaths, + onClick +}) => { + const isActive = activePaths && includes(activePaths, path) + const isRootParent = + activePaths && activePaths.length > 1 && activePaths[0] === path + + return ( + <> + + {label} + + {children && ( + + {children.map(item => ( + + ))} + + )} + + ) +} + +interface ISidebarMenuProps { + currentPath: string + onClick: (e: React.MouseEvent) => void +} + +const SidebarMenu: React.SFC = ({ + currentPath, + onClick +}) => { + const location = useLocation() + const rootRef = useRef(null) + const psRef = useRef(undefined) + const [isScrollHidden, setIsScrollHidden] = useState(false) + const activePaths = currentPath && getParentsListFromPath(currentPath) + + const scrollToActiveItem = (): void => { + const node = document.getElementById(currentPath) + const parent = rootRef.current + + setIsScrollHidden(true) + setTimeout(() => { + psRef.current?.update() + + if (node && parent) { + const parentHeight = parent.clientHeight + const parentScroll = parent.scrollTop + const nodeOffset = node.offsetTop + const nodeHeight = node.clientHeight + const scrollOffset = nodeOffset - parentHeight + nodeHeight + + if ( + parentScroll > nodeOffset + nodeHeight || + parentScroll + parentHeight < nodeOffset + ) { + parent.scrollTop = scrollOffset + } + } + + setIsScrollHidden(false) + }, 400) + } + + useEffect(() => { + if (!psRef.current && rootRef.current) { + psRef.current = new PerfectScrollbar(rootRef.current, { + wheelPropagation: true + }) + } + + scrollToActiveItem() + + return (): void => { + psRef.current?.destroy() + psRef.current = undefined + } + }, []) + useEffect(scrollToActiveItem, [location.pathname]) + + return ( +
+
+
+ {structure.map(item => ( + + ))} +
+
+ +
+ +
+
+
+ ) +} + +export default SidebarMenu diff --git a/src/components/Documentation/Layout/SidebarMenu/styles.js b/src/components/Documentation/Layout/SidebarMenu/styles.js deleted file mode 100644 index 2253caaa33..0000000000 --- a/src/components/Documentation/Layout/SidebarMenu/styles.js +++ /dev/null @@ -1,99 +0,0 @@ -import styled from 'styled-components' - -import { media } from '../../../../styles' - -export const Menu = styled.div` - position: sticky; - top: calc(var(--layout-header-height-scrolled) + 60px); - height: calc(100vh - var(--layout-header-height-scrolled) - 60px); - overflow-y: hidden; - -webkit-overflow-scrolling: touch; - - .ReactCollapse--collapse { - padding-left: 20px; - transition: height 250ms; - } - - ${props => - props.isScrollHidden && - ` - .ps__rail-y { opacity: 0; overflow: hidden; } - `}; - - ${media.phablet` - position: relative; - top: 0; - height: calc(100% - 60px); - padding-left: 20px; - `}; -` - -export const Sections = styled.div` - margin-bottom: 25px; - margin-top: 10px; - min-width: 280px; - - ${media.phablet` - min-width: auto; - `} -` - -export const SectionLinks = styled.div` - @media (max-width: 768px) { - position: relative; - } -` - -const sectionLinkColor = '#b0b8c5' - -export const SectionLink = styled.a` - display: block; - position: relative; - font-size: 18px; - font-weight: 500; - color: #b0b8c5; - color: ${sectionLinkColor}; - text-decoration: none; - font-weight: 400; - line-height: 26px; - min-height: 26px; - padding-bottom: 5px; - padding-left: 15px; - margin: 0 0 0 5px; - - ${props => - props.isActive && - ` - color: #40364d; - `}; - - &:hover { - color: #3c3937; - - ${media.phablet` - color: ${sectionLinkColor}; - `} - } - - &::before { - content: ''; - display: block; - position: absolute; - width: 8px; - height: 5px; - background: url('/img/triangle_dark.svg') no-repeat center center; - left: 0px; - top: 10px; - - ${props => - props.isActive && - ` - transform: rotate(-90deg); - `}; - } -` - -export const SideFooter = styled.div` - margin-top: 30px; - padding-bottom: 30px; -` diff --git a/src/components/Documentation/Layout/SidebarMenu/styles.module.css b/src/components/Documentation/Layout/SidebarMenu/styles.module.css new file mode 100644 index 0000000000..23764e27d8 --- /dev/null +++ b/src/components/Documentation/Layout/SidebarMenu/styles.module.css @@ -0,0 +1,88 @@ +.menu { + position: sticky; + top: calc(var(--layout-header-height-collapsed) + 60px); + height: calc(100vh - var(--layout-header-height-collapsed) - 60px); + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + + &.isScrollHidden { + .ps__rail-y { + opacity: 0; + overflow: hidden; + } + } + + @media (--xs-scr) { + position: relative; + top: 0; + height: calc(100% - 60px); + padding-left: 20px; + } + + :global(.ReactCollapse--collapse) { + padding-left: 20px; + transition: height 250ms; + } +} + +.sections { + min-width: 280px; + margin-bottom: 25px; + margin-top: 10px; + + @media (--xs-scr) { + min-width: auto; + } +} + +.sectionLinks { + @media (--sm-scr) { + position: relative; + } +} + +.sectionLink { + display: block; + position: relative; + min-height: 26px; + margin: 0 0 0 5px; + padding-left: 15px; + padding-bottom: 5px; + font-size: 18px; + line-height: 26px; + font-weight: 400; + text-decoration: none; + color: var(--color-gray-light); + + &.active { + color: var(--color-gray-hover); + } + + &:hover { + color: #3c3937; + + @media (--xs-scr) { + color: var(--color-gray-light); + } + } + + &::before { + content: ''; + position: absolute; + left: 0; + top: 10px; + display: block; + height: 5px; + width: 8px; + background: url('/img/triangle_dark.svg') no-repeat center center; + + &.active { + transform: rotate(-90deg); + } + } +} + +.footer { + margin-top: 30px; + padding-bottom: 30px; +} diff --git a/src/components/Documentation/Layout/index.js b/src/components/Documentation/Layout/index.js deleted file mode 100644 index abd03e5f2d..0000000000 --- a/src/components/Documentation/Layout/index.js +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useCallback, useState } from 'react' -import PropTypes from 'prop-types' -import MainLayout, { LayoutModifiers } from '../../MainLayout' -import Hamburger from '../../Hamburger' -import SearchForm from '../../SearchForm' -import SidebarMenu from './SidebarMenu' - -import { Container, Backdrop, Side, SideToggle } from './styles' - -import { structure } from '../../../utils/sidebar' - -const SIDEBAR_MENU = 'sidebar-menu' - -function Layout({ children, ...restProps }) { - const [isMenuOpen, setIsMenuOpen] = useState(false) - - const toggleMenu = useCallback(() => setIsMenuOpen(!isMenuOpen), [isMenuOpen]) - - return ( - - - - - - - - - - - - - {children} - - - ) -} - -Layout.propTypes = { - children: PropTypes.element.isRequired, - location: PropTypes.shape({ - pathname: PropTypes.string.isRequired - }) -} - -export default Layout diff --git a/src/components/Documentation/Layout/index.tsx b/src/components/Documentation/Layout/index.tsx new file mode 100644 index 0000000000..88fefd0e6b --- /dev/null +++ b/src/components/Documentation/Layout/index.tsx @@ -0,0 +1,55 @@ +import React, { useCallback, useState } from 'react' +import cn from 'classnames' + +import MainLayout, { LayoutComponent, LayoutModifiers } from '../../MainLayout' +import LayoutWidthContainer from '../../LayoutWidthContainer' +import HamburgerIcon from '../../HamburgerIcon' +import SearchForm from './SearchForm' +import SidebarMenu from './SidebarMenu' +import { matchMedia } from '../../../utils/front/breakpoints' + +import styles from './styles.module.css' + +const Layout: LayoutComponent = ({ children, ...restProps }) => { + const [isMenuOpen, setIsMenuOpen] = useState(false) + + const toggleMenu = useCallback(() => setIsMenuOpen(!isMenuOpen), [isMenuOpen]) + + return ( + + + {/* eslint-disable jsx-a11y/no-static-element-interactions */} + {/* eslint-disable jsx-a11y/click-events-have-key-events */} +
+ {/* eslint-enable jsx-a11y/no-static-element-interactions */} + {/* eslint-enable jsx-a11y/click-events-have-key-events */} + + + +
+ + + matchMedia('--xs-scr') ? toggleMenu() : undefined + } + /> +
+
{children}
+ + + ) +} + +export default Layout diff --git a/src/components/Documentation/Layout/styles.js b/src/components/Documentation/Layout/styles.module.css similarity index 55% rename from src/components/Documentation/Layout/styles.js rename to src/components/Documentation/Layout/styles.module.css index 10c4c7ff7e..5d4c1d2246 100644 --- a/src/components/Documentation/Layout/styles.js +++ b/src/components/Documentation/Layout/styles.module.css @@ -1,64 +1,60 @@ -import styled from 'styled-components' - -import { media } from '../../../styles' - -export const Container = styled.div` +.container { display: flex; flex-direction: row; - max-width: 1200px; - margin: 0 auto; - background: white; z-index: 2; - &:before { + @media (--xs-scr) { + padding: 0 15px; + } + + &::before { content: ''; display: block; position: fixed; + z-index: -1; top: 0; left: 0; bottom: 0; width: 50%; - background-color: #eef4f8; - z-index: -1; pointer-events: none; + background-color: var(--color-light-blue); + + @media (--xs-scr) { + display: none; + } } -` +} -export const Backdrop = styled.div` +.backdrop { display: none; - ${media.phablet` + @media (--xs-scr) { display: block; opacity: 0; pointer-events: none; - transition: opacity .3s linear; + transition: opacity 0.3s linear; - ${props => - props.visible && - ` + &.opened { content: ''; position: fixed; top: 0; left: 0; bottom: 0; right: 0; - background-color: rgba(0, 0, 0, 0.4); z-index: 1; opacity: 1; pointer-events: all; - `} - `}; -` + background-color: rgba(0, 0, 0, 0.4); + } + } +} -export const Side = styled.div` +.side { + flex-shrink: 0; width: 280px; - background-color: #eef4f8; + background-color: var(--color-light-blue); - @media only screen and (max-width: 1200px) { - padding-left: 15px; - } - - ${media.phablet` + @media (--xs-scr) { position: fixed; display: block; z-index: 2; @@ -66,44 +62,52 @@ export const Side = styled.div` bottom: 0; left: 0; right: 60px; - box-shadow: rgba(0, 0, 0, 0.14) 0px 0px 4px, rgba(0, 0, 0, 0.28) 0px 4px 8px; + box-shadow: rgba(0, 0, 0, 0.14) 0 0 4px, rgba(0, 0, 0, 0.28) 0 4px 8px; transform: translateX(-110%); - transition: transform .35s ease; + transition: transform 0.35s ease; - ${props => - props.isOpen && - ` + &.opened { transform: translateX(0); - `} - `}; -` + } + } +} -export const SideToggle = styled.div` +.sideToggle { display: none; + justify-content: center; + align-items: center; position: fixed; z-index: 2; left: 8px; bottom: 20px; width: 45px; height: 45px; + border: none; border-radius: 50%; background-color: rgba(255, 255, 255, 0.9); - box-shadow: 0 0px 9px 0 rgba(0, 0, 0, 0.15); + box-shadow: 0 0 9px 0 rgba(0, 0, 0, 0.15); transition: transform 0.3s ease; - justify-content: center; - align-items: center; - ${media.phablet` + &.opened { + transform: translateX(calc(100vw - 60px)); + } + + @media (--xs-scr) { display: flex; - > div { + > * { transform: scale(0.75); } - `}; + } +} - ${({ isMenuOpen }) => - isMenuOpen && - ` - transform: translateX(calc(100vw - 60px)); - `}; -` +.content { + display: flex; + flex-direction: row; + max-width: calc(100% - 280px); + background-color: #fff; + + @media (--xs-scr) { + max-width: 100%; + } +} diff --git a/src/components/Documentation/Markdown/Tooltip/DesktopView/index.tsx b/src/components/Documentation/Markdown/Tooltip/DesktopView/index.tsx new file mode 100644 index 0000000000..5c3fb7c28d --- /dev/null +++ b/src/components/Documentation/Markdown/Tooltip/DesktopView/index.tsx @@ -0,0 +1,134 @@ +import React, { useRef, useState, useEffect } from 'react' +import cn from 'classnames' +import ReactMarkdown from 'react-markdown' +import Portal from '@reach/portal' +import throttle from 'lodash.throttle' + +import { getHeaderHeight } from '../../../../../utils/front/scroll' +import styles from './styles.module.css' + +interface IDesktopViewProps { + description: string + header: string + text: React.ReactNode +} + +interface ITooltipPosition { + left: number + top: number + arrow: ['l' | 'r', 't' | 'b'] +} + +const ARROW_SIZE = 10 + +const getPosition = (toggle: Element, tooltip: Element): ITooltipPosition => { + const toggleRect = toggle.getBoundingClientRect() + const tooltipRect = tooltip.getBoundingClientRect() + const windowWidth = document.documentElement.clientWidth + const headerHeight = getHeaderHeight() + const result: ITooltipPosition = { left: 0, top: 0, arrow: ['l', 'b'] } + + if (windowWidth - tooltipRect.width > toggleRect.left) { + result.left = toggleRect.left + } else { + result.left = toggleRect.left + toggleRect.width - tooltipRect.width + result.arrow[0] = 'r' + } + + if (toggleRect.top > tooltipRect.height + ARROW_SIZE + headerHeight) { + result.top = toggleRect.top - tooltipRect.height - ARROW_SIZE + } else { + result.top = toggleRect.top + toggleRect.height + ARROW_SIZE + result.arrow[1] = 't' + } + + return result +} + +const DesktopView: React.SFC = ({ + description, + header, + text +}) => { + const timeoutRef = useRef() + const toggleRef = useRef(null) + const tooltipRef = useRef(null) + const [tooltipPosition, setPosition] = useState< + ITooltipPosition | undefined + >() + const [isVisible, setVisible] = useState(false) + const calcPosition = (): void => { + if (!tooltipRef.current || !toggleRef.current) { + return + } + + setPosition(getPosition(toggleRef.current, tooltipRef.current)) + } + const throttledCalcPosition = throttle(calcPosition, 50) + const show = (): void => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + timeoutRef.current = undefined + } + + setVisible(true) + } + const hide = (): void => { + timeoutRef.current = window.setTimeout(() => setVisible(false), 100) + } + + useEffect(() => { + document.addEventListener('scroll', throttledCalcPosition) + window.addEventListener('resize', throttledCalcPosition) + + return (): void => { + document.removeEventListener('scroll', throttledCalcPosition) + window.removeEventListener('resize', throttledCalcPosition) + } + }, []) + useEffect(() => { + if (isVisible) { + requestAnimationFrame(calcPosition) + } + }, [isVisible]) + + return ( + <> + {isVisible && ( + +
+
{header}
+ +
+
+ )} + + {text} + + + ) +} + +export default DesktopView diff --git a/src/components/Documentation/Markdown/Tooltip/DesktopView/styles.module.css b/src/components/Documentation/Markdown/Tooltip/DesktopView/styles.module.css new file mode 100644 index 0000000000..440aced6cb --- /dev/null +++ b/src/components/Documentation/Markdown/Tooltip/DesktopView/styles.module.css @@ -0,0 +1,77 @@ +.highlightedText { + border-bottom: 1px black dotted; +} + +.tooltip { + box-sizing: border-box; + position: fixed; + top: 0; + left: 0; + display: block; + opacity: 0; + width: 400px; + padding: 10px 10px 16px; + border: 1px solid var(--color-lighter-blue); + border-radius: 3px; + font-family: var(--font-brandon); + color: var(--color-black); + background-color: #fff; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + + &.calculated { + opacity: 1; + } + + &::after { + position: absolute; + content: ''; + display: block; + height: 12px; + width: 12px; + border: none; + border-top: 1px solid var(--color-lighter-blue); + border-left: 2px solid var(--color-lighter-blue); + transform: rotate(90deg); + background-color: #fff; + } + + &.lt::after, + &.rt::after { + top: -8px; + border-top-width: 2px; + border-left-width: 1px; + transform: rotate(45deg); + } + + &.lt::after { + left: 27px; + } + + &.rt::after { + right: 27px; + } + + &.lb::after, + &.rb::after { + top: 100%; + margin-top: -5px; + transform: rotate(-135deg); + } + + &.lb::after { + left: 27px; + } + + &.rb::after { + right: 27px; + } +} + +.tooltipHeader { + font-size: 1.5em; + font-weight: bold; +} + +.tooltipBody { + font-size: 1.1em; +} diff --git a/src/components/Documentation/Markdown/Tooltip/MobileView/index.tsx b/src/components/Documentation/Markdown/Tooltip/MobileView/index.tsx new file mode 100644 index 0000000000..0a69f64c5d --- /dev/null +++ b/src/components/Documentation/Markdown/Tooltip/MobileView/index.tsx @@ -0,0 +1,80 @@ +import React, { useState } from 'react' +import cn from 'classnames' +import Portal from '@reach/portal' +import ReactMarkdown from 'react-markdown' + +import { isTriggeredFromKB } from '../../../../../utils/front/keyboard' + +import styles from './styles.module.css' + +interface IMobileViewProps { + description: string + header: string + text: React.ReactNode +} + +const MobileView: React.SFC = ({ + description, + header, + text +}) => { + const [isVisible, setVisible] = useState(false) + const openTooltip = (e: React.MouseEvent): void => { + e.stopPropagation() + setVisible(true) + } + const onOpenKeyDown = (e: React.KeyboardEvent): void => { + if (isTriggeredFromKB(e)) { + setVisible(true) + } + } + const closeTooltip = (e: React.MouseEvent): void => { + e.stopPropagation() + setVisible(false) + } + const onCloseKeyDown = (e: React.KeyboardEvent): void => { + if (isTriggeredFromKB(e)) { + setVisible(false) + } + } + + return ( + <> + + {text} + + {isVisible && ( + + {/* eslint-disable jsx-a11y/no-static-element-interactions*/} + {/* eslint-disable jsx-a11y/click-events-have-key-events */} +
+ {/* eslint-enable jsx-a11y/no-static-element-interactions*/} + {/* eslint-enable jsx-a11y/click-events-have-key-events */} +
+
+
+
+
+
{header}
+ +
+
+ + )} + + ) +} + +export default MobileView diff --git a/src/components/Documentation/Markdown/Tooltip/MobileView/styles.module.css b/src/components/Documentation/Markdown/Tooltip/MobileView/styles.module.css new file mode 100644 index 0000000000..f7eb3496fa --- /dev/null +++ b/src/components/Documentation/Markdown/Tooltip/MobileView/styles.module.css @@ -0,0 +1,49 @@ +.highlightedText { + border-bottom: 1px black dotted; +} + +.modalBackground { + position: fixed; + z-index: 100; + top: 0; + left: 0; + height: 100vh; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: rgba(0, 0, 0, 0.4); +} + +.closeContainer { + float: right; + margin: 2px 10px 0 0; +} + +.closeLine { + position: absolute; + height: 23px; + width: 2px; + background-color: black; + + &.first { + transform: rotate(-45deg); + } + + &.second { + transform: rotate(45deg); + } +} + +.modalContent { + width: 80%; + padding: 8px 10px; + border: 1px solid var(--color-light-blue); + border-radius: 3px; + background-color: #fff; +} + +.modalHeader { + font-size: 1.3em; + font-weight: bold; +} diff --git a/src/components/Documentation/Markdown/Tooltip/index.tsx b/src/components/Documentation/Markdown/Tooltip/index.tsx new file mode 100644 index 0000000000..f4c0861d24 --- /dev/null +++ b/src/components/Documentation/Markdown/Tooltip/index.tsx @@ -0,0 +1,58 @@ +import React, { useState, useEffect } from 'react' +import includes from 'lodash.includes' + +import ShowOnly from '../../../ShowOnly' +import DesktopView from './DesktopView' +import MobileView from './MobileView' + +import glossary from '../../../../../content/docs/glossary' + +const Tooltip: React.SFC<{ text: string }> = ({ text }) => { + const [state, setState] = useState({ + description: '', + header: '', + match: false + }) + + useEffect(() => { + glossary.contents.forEach(glossaryItem => { + if ( + includes( + glossaryItem.match.map(word => word.toLowerCase()), + text.replace(/\n/g, ' ').toLowerCase() + ) + ) { + setState({ + description: glossaryItem.desc, + header: glossaryItem.name, + match: true + }) + } + }) + }, [text]) + + if (!state.match) { + return {text} + } + + return ( + <> + + + + + + + + ) +} + +export default Tooltip diff --git a/src/components/Documentation/Markdown/index.js b/src/components/Documentation/Markdown/index.js deleted file mode 100644 index 3ff1b3f597..0000000000 --- a/src/components/Documentation/Markdown/index.js +++ /dev/null @@ -1,150 +0,0 @@ -import React, { useCallback, useEffect, useRef } from 'react' -import rehypeReact from 'rehype-react' -import PropTypes from 'prop-types' -import Collapsible from 'react-collapsible' - -import 'github-markdown-css/github-markdown.css' - -import { navigate } from '@reach/router' - -import { getPathWithSoruce } from '../../../utils/sidebar' - -import Link from '../../Link' -import Tooltip from '../../Tooltip' - -import Tutorials from '../Tutorials' - -import { - Button, - Content, - GithubLink, - NavigationButtons, - TutorialsWrapper -} from './styles' - -const isInsideCodeBlock = elem => { - for (let el = elem; el && el !== document; el = el.parentNode) { - if (el.tagName === 'PRE') return true - if (el.tagName === 'ARTICLE') return false - } - return false -} - -function Details({ children }) { - const filteredChildren = children.filter(child => child !== '\n') - - const text = filteredChildren[0].props.children[0] - return ( - - {filteredChildren.slice(1)} - - ) -} - -Details.propTypes = { - children: PropTypes.node -} - -function ABBR({ children }) { - return -} - -ABBR.propTypes = { - children: PropTypes.node -} - -const renderAst = new rehypeReact({ - createElement: React.createElement, - Fragment: React.Fragment, - components: { details: Details, abbr: ABBR, a: Link } -}).Compiler - -export default function Markdown({ - htmlAst, - prev, - next, - tutorials, - githubLink -}) { - const touchstartXRef = useRef(0) - const touchendXRef = useRef(0) - const isCodeBlockRef = useRef(false) - - const handleSwipeGesture = useCallback(() => { - if (isCodeBlockRef.current) return - - if (touchstartXRef.current - touchendXRef.current > 100) { - navigate(next) - } - - if (touchendXRef.current - touchstartXRef.current > 100) { - navigate(prev) - } - }, [prev, next]) - - const onTouchStart = useCallback(e => { - isCodeBlockRef.current = isInsideCodeBlock(e.target) - touchstartXRef.current = event.changedTouches[0].screenX - }, []) - - const onTouchEnd = useCallback(() => { - touchendXRef.current = event.changedTouches[0].screenX - handleSwipeGesture() - }, []) - - useEffect(() => { - document.addEventListener('touchstart', onTouchStart, false) - document.addEventListener('touchend', onTouchEnd, false) - - return () => { - document.removeEventListener('touchstart', onTouchStart) - document.removeEventListener('touchend', onTouchEnd) - } - }, []) - - return ( - - {tutorials && ( - - - - )} - - Edit on GitHub - -
{renderAst(htmlAst)}
- - {prev ? ( - - - Prev - - ) : ( - - )} - {next ? ( - - Next - - - ) : ( - - )} - -
- ) -} - -Markdown.propTypes = { - htmlAst: PropTypes.object.isRequired, - githubLink: PropTypes.string.isRequired, - tutorials: PropTypes.object, - prev: PropTypes.string, - next: PropTypes.string -} diff --git a/src/components/Documentation/Markdown/index.tsx b/src/components/Documentation/Markdown/index.tsx new file mode 100644 index 0000000000..6a99bc7feb --- /dev/null +++ b/src/components/Documentation/Markdown/index.tsx @@ -0,0 +1,141 @@ +import React, { useCallback, useEffect, useRef } from 'react' +import cn from 'classnames' +import { navigate } from '@reach/router' +import rehypeReact from 'rehype-react' +import Collapsible from 'react-collapsible' + +import Link from '../../Link' +import Tooltip from './Tooltip' +import Tutorials from '../TutorialsLinks' +import { getPathWithSoruce } from '../../../utils/shared/sidebar' + +import 'github-markdown-css/github-markdown.css' +import sharedStyles from '../styles.module.css' +import styles from './styles.module.css' + +const isInsideCodeBlock = (node: Element): boolean => { + while (node?.parentNode) { + if (node.tagName === 'PRE') { + return true + } + + if (node.tagName === 'ARTICLE') { + return false + } + + node = node.parentNode as Element + } + + return false +} + +const Details: React.SFC<{ + children: Array<{ props: { children: Array } } | string> +}> = ({ children }) => { + const filteredChildren = children.filter(child => child !== '\n') + + if (!filteredChildren.length) return null + if (typeof filteredChildren[0] === 'string') return null + + const text = filteredChildren[0].props.children[0] + + return ( + + {filteredChildren.slice(1)} + + ) +} + +const Abbr: React.SFC<{ children: [string] }> = ({ children }) => { + return +} + +const renderAst = new rehypeReact({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + createElement: React.createElement as any, + Fragment: React.Fragment, + components: { details: Details, abbr: Abbr, a: Link } +}).Compiler + +interface IMarkdownProps { + htmlAst: object + githubLink: string + tutorials: { [type: string]: string } + prev?: string + next?: string +} + +const Markdown: React.SFC = ({ + htmlAst, + prev, + next, + tutorials, + githubLink +}) => { + const touchstartXRef = useRef(0) + const touchendXRef = useRef(0) + const isCodeBlockRef = useRef(false) + const handleSwipeGesture = useCallback(() => { + if (isCodeBlockRef.current) return + + if (next && touchstartXRef.current - touchendXRef.current > 100) { + navigate(next) + } + + if (prev && touchendXRef.current - touchstartXRef.current > 100) { + navigate(prev) + } + }, [prev, next]) + const onTouchStart = useCallback(e => { + isCodeBlockRef.current = isInsideCodeBlock(e.target) + touchstartXRef.current = e.changedTouches[0].screenX + }, []) + const onTouchEnd = useCallback(e => { + touchendXRef.current = e.changedTouches[0].screenX + handleSwipeGesture() + }, []) + + useEffect(() => { + document.addEventListener('touchstart', onTouchStart, false) + document.addEventListener('touchend', onTouchEnd, false) + + return (): void => { + document.removeEventListener('touchstart', onTouchStart) + document.removeEventListener('touchend', onTouchEnd) + } + }, []) + + return ( +
+ {tutorials && ( +
+ +
+ )} + + Edit on + GitHub + +
{renderAst(htmlAst)}
+
+ + + Prev + + + Next + + +
+
+ ) +} + +export default Markdown diff --git a/src/components/Documentation/Markdown/styles.js b/src/components/Documentation/Markdown/styles.module.css similarity index 75% rename from src/components/Documentation/Markdown/styles.js rename to src/components/Documentation/Markdown/styles.module.css index a5972137cb..7b6acba588 100644 --- a/src/components/Documentation/Markdown/styles.js +++ b/src/components/Documentation/Markdown/styles.module.css @@ -1,18 +1,4 @@ -import styled from 'styled-components' - -import { media } from '../../../styles' - -import { LightButton } from '../styles' - -export const Content = styled.article` - min-width: 200px; - margin: 30px; - flex: 1; - - ${media.phablet` - margin: 20px; - `}; - +:global { ul { list-style-type: disc; } @@ -25,12 +11,28 @@ export const Content = styled.article` font-style: italic; } + #markdown-root { + @keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } + } + + animation-duration: 1s; + animation-fill-mode: both; + + :local { + animation-name: fadeIn; + } + } + .markdown-body { font-family: inherit; font-size: 18px; - animation-duration: 1s; - animation-fill-mode: both; - animation-name: fadeIn; h1, h2, @@ -41,7 +43,7 @@ export const Content = styled.article` font-weight: 500; } - a[target='_blank']:after { + a[target='_blank']::after { position: relative; top: 1px; right: 0; @@ -183,14 +185,14 @@ export const Content = styled.article` } .Collapsible__trigger { - font-family: BrandonGrotesque; + font-family: var(--font-brandon); font-weight: 500; display: block; position: relative; opacity: 0.9; cursor: pointer; - &:after { + &::after { position: absolute; display: inline-block; background-size: 20px 20px; @@ -203,10 +205,8 @@ export const Content = styled.article` transition: transform 200ms; } - &.is-open { - &:after { - opacity: 0.5; - } + &.is-open::after { + opacity: 0.5; } } @@ -217,128 +217,117 @@ export const Content = styled.article` padding: 10px; } - @keyframes fadeIn { - from { - opacity: 0; - } - - to { - opacity: 1; - } - } - details p { font-size: 17px; - color: #454e53; + color: var(--color-gray-dark); margin-left: 20px; margin-right: 10px; } details pre { font-size: 14px; - color: #454e53; + color: var(--color-gray-dark); margin-left: 20px; margin-right: 10px; } -` - -export const NavigationButtons = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - margin-top: 40px; - font-weight: 600; - font-size: 14px; -` - -export const Button = styled.a` - text-decoration: none; - background: white; - padding: 10px 15px; - text-transform: uppercase; - color: #333; - border-bottom: 3px solid #13adc7; - display: inline-flex; - align-items: center; - transition: 0.2s border-color ease-out; +} - &:hover { - border-bottom: 3px solid #11849b; - } - - i { - display: inline-block; - background-image: url(/img/arrow.svg); - background-size: contain; - background-position: center; - background-repeat: no-repeat; - width: 1em; - height: 1em; - line-height: 1; - transition: all 0.3s; - - &.next { - margin-left: 7px; - } - - &.prev { - margin-right: 7px; - mask-position: center; - transform: rotate(180deg); - margin-top: 2px; - } - } +.content { + min-width: 200px; + margin: 30px; + flex: 1; + background-color: #fff; - &[disabled] { - pointer-events: none; - opacity: 0.5; + @media (--xs-scr) { + margin: 15px 0; } -` +} -export const TutorialsWrapper = styled.div` +.tutorialsWrapper { position: relative; z-index: 1; float: right; margin: 5px 0 0 10px; - ${media.tablet` + @media (--md-scr) { margin: 0 0 15px 0; - `} + } @media only screen and (min-width: 1200px) { display: none; } -` +} -export const GithubLink = styled(LightButton)` +.githubLink { display: none; float: right; margin: 5px 0 10px 10px; z-index: 1; position: relative; - ${media.tablet` + @media (--md-scr) { float: none; margin: 0 0 15px 0; - `}; + } @media only screen and (max-width: 1200px) { display: inline-flex; } +} + +.githubIcon { + background-image: url(/img/github_icon.svg); +} - i { - background-image: url(/img/github_icon.svg); +.navButtons { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 40px; + font-size: 14px; + font-weight: 600; +} + +.navButton { + text-decoration: none; + background: white; + padding: 10px 15px; + text-transform: uppercase; + color: #333; + border-bottom: 3px solid #13adc7; + display: inline-flex; + align-items: center; + transition: 0.2s border-color ease-out; + + &:hover { + border-bottom: 3px solid #11849b; } -` -export const ExternalLink = styled.a` - &:after { - position: relative; - top: 1px; - right: 0; - width: 12px; - height: 12px; - margin-left: 1px; - content: url(/img/external-link.svg); + &[disabled] { + pointer-events: none; + opacity: 0.5; + } +} + +.navButtonIcon { + display: inline-block; + width: 1em; + height: 1em; + line-height: 1; + background-image: url(/img/arrow.svg); + background-size: contain; + background-position: center; + background-repeat: no-repeat; + transition: all 0.3s; + + &.next { + margin-left: 7px; + } + + &.prev { + margin-right: 7px; + mask-position: center; + transform: rotate(180deg); + margin-top: 2px; } -` +} diff --git a/src/components/Documentation/RightPanel/index.js b/src/components/Documentation/RightPanel/index.js deleted file mode 100644 index 0cd9ce5354..0000000000 --- a/src/components/Documentation/RightPanel/index.js +++ /dev/null @@ -1,162 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import throttle from 'lodash.throttle' - -import Link from '../../Link' - -import Tutorials from '../Tutorials' - -import { allImagesLoadedInContainer } from '../../../utils/images' - -import { - Description, - DiscordButton, - GithubButton, - Header, - HeadingLink, - Spacer, - Wrapper -} from './styles' - -const MARKDOWN_ROOT = '#markdown-root' - -export default class RightPanel extends React.PureComponent { - state = { - height: 0, - coordinates: {}, - current: undefined - } - componentDidMount() { - this.root = document.documentElement - - if (this.props.headings.length) { - this.initHeadingsPosition() - } - - document.addEventListener('scroll', this.setCurrentHeader) - window.addEventListener('resize', this.updateHeadingsPosition) - } - - componentDidUpdate(prevProps) { - if (this.props.headings !== prevProps.headings) { - this.initHeadingsPosition() - } - } - - componentWillUnmount() { - document.removeEventListener('scroll', this.setCurrentHeader) - window.removeEventListener('resize', this.updateHeadingsPosition) - } - - initHeadingsPosition = () => - allImagesLoadedInContainer(document.querySelector(MARKDOWN_ROOT)).then( - this.updateHeadingsPosition - ) - - updateHeadingsPosition = () => { - const coordinates = this.props.headings.reduce((result, { slug }) => { - const headingElement = document.getElementById(slug) - - return { ...result, [headingElement && headingElement.offsetTop]: slug } - }, {}) - - const height = this.root.clientHeight - - this.setState({ coordinates, height }, this.setCurrentHeader) - } - - setCurrentHeader = throttle(() => { - const { coordinates, height } = this.state - const { scrollTop } = this.root - const coordinateKeys = Object.keys(coordinates) - - if (!coordinateKeys.length) return - - const filteredKeys = coordinateKeys.filter( - top => top <= scrollTop + height / 2 - ) - - const current = filteredKeys.length - ? coordinates[filteredKeys[filteredKeys.length - 1]] - : undefined - - this.setState({ current }) - }, 100) - - render() { - const { headings, githubLink, tutorials } = this.props - const { current } = this.state - - return ( - - {!headings.length && } - {headings.length > 0 && ( - <> -
Content
-
- {headings.map(({ slug, text }) => ( - - {text} - - ))} -
- - )} - {Object.keys(tutorials || {}).length > 0 && ( - <> - - - ▶️ - {' '} - It can be run online: - - -
- - )} - - - 🐛 - {' '} - Found an issue? Let us know! Or fix it: - - - - - Edit on GitHub - - -
-
- - - ❓ - {' '} - Have a question? Join our chat, we will help you: - - - - - Discord Chat - -
- ) - } -} - -RightPanel.propTypes = { - headings: PropTypes.arrayOf( - PropTypes.shape({ - slug: PropTypes.string.isRequired, - text: PropTypes.string.isRequired - }) - ).isRequired, - tutorials: PropTypes.object, - githubLink: PropTypes.string.isRequired -} diff --git a/src/components/Documentation/RightPanel/index.tsx b/src/components/Documentation/RightPanel/index.tsx new file mode 100644 index 0000000000..a2e75ae6e5 --- /dev/null +++ b/src/components/Documentation/RightPanel/index.tsx @@ -0,0 +1,178 @@ +import React, { useState, useEffect } from 'react' +import cn from 'classnames' +import throttle from 'lodash.throttle' + +import { IHeading } from '../' +import Link from '../../Link' +import Tutorials from '../TutorialsLinks' + +import { getHeaderHeight } from '../../../utils/front/scroll' +import { allImagesLoadedInContainer } from '../../../utils/front/images' + +import sharedStyles from '../styles.module.css' +import styles from './styles.module.css' + +interface IRightPanelProps { + headings: Array + tutorials: { [type: string]: string } + githubLink: string +} + +interface IHeadingsCoordinates { + [offset: string]: string +} + +const RightPanel: React.SFC = ({ + headings, + tutorials, + githubLink +}) => { + const [height, setHeight] = useState(0) + const [headingsOffsets, setHeadingsOffsets] = useState( + {} + ) + const [current, setCurrent] = useState(null) + const setCurrentHeader = (): void => { + const { scrollTop } = document.documentElement + const coordinateKeys = Object.keys(headingsOffsets) + + if (!coordinateKeys.length) return + + const headerHeight = getHeaderHeight() + const filteredKeys = coordinateKeys.filter( + offsetTop => + parseInt(offsetTop, 10) <= scrollTop + (height - headerHeight) / 2 + ) + + const newCurrent = filteredKeys.length + ? headingsOffsets[filteredKeys[filteredKeys.length - 1]] + : null + + setCurrent(newCurrent) + } + + const updateHeadingsPosition = (): void => { + const offsets = headings.reduce( + (result: IHeadingsCoordinates, heading: IHeading) => { + const headingElement = document.getElementById(heading.slug) + + if (headingElement?.offsetTop) { + result[headingElement.offsetTop.toString()] = heading.slug + } + + return result + }, + {} + ) + setHeadingsOffsets(offsets) + setHeight(document.documentElement.clientHeight) + } + + const initHeadingsPosition = (): void => { + const root = document.querySelector('#markdown-root') + + root && allImagesLoadedInContainer(root).then(updateHeadingsPosition) + } + + useEffect(() => { + const throttledSetCurrentHeader = throttle(setCurrentHeader, 100) + + document.addEventListener('scroll', throttledSetCurrentHeader) + window.addEventListener('resize', updateHeadingsPosition) + + return (): void => { + document.removeEventListener('scroll', throttledSetCurrentHeader) + window.removeEventListener('resize', updateHeadingsPosition) + } + }, [setCurrentHeader]) + useEffect(initHeadingsPosition, [headings]) + useEffect(setCurrentHeader, [headingsOffsets, height]) + + return ( +
+
+ {headings.length > 0 && ( + <> +
Content
+
+ + )} + {headings.map(({ slug, text }) => ( + + {text} + + ))} +
+ {Object.keys(tutorials || {}).length > 0 && ( +
+

+ + ▶️ + {' '} + It can be run online: +

+ +
+ )} +
+

+ + 🐛 + {' '} + Found an issue? Let us know! Or fix it: +

+ + + + Edit on GitHub + +
+ +
+

+ + ❓ + {' '} + Have a question? Join our chat, we will help you: +

+ + + + Discord Chat + +
+
+ ) +} + +export default RightPanel diff --git a/src/components/Documentation/RightPanel/styles.js b/src/components/Documentation/RightPanel/styles.js deleted file mode 100644 index e53b03b49d..0000000000 --- a/src/components/Documentation/RightPanel/styles.js +++ /dev/null @@ -1,79 +0,0 @@ -import styled from 'styled-components' - -import { LightButton } from '../styles' - -export const Wrapper = styled.div` - width: 170px; - min-width: 170px; - font-size: 16px; - height: calc(100vh - 78px); - position: sticky; - top: var(--layout-header-height-scrolled); - - @media only screen and (max-width: 1200px) { - display: none; - } - - hr { - opacity: 0.5; - } -` - -export const Header = styled.p` - color: #3c3937; - font-size: 14px; - text-transform: uppercase; - margin-top: 56px; -` - -export const HeadingLink = styled.a` - display: block; - position: relative; - font-size: 16px; - font-weight: 500; - color: #a0a8a5; - text-decoration: none; - font-weight: 400; - line-height: 26px; - min-height: 26px; - margin-bottom: 3px; - cursor: pointer; - - ${props => - props.isCurrent && - ` - color: #000; - `} - - &:hover { - color: #3c3937; - } -` - -export const ExternalButton = styled(LightButton)` - box-sizing: border-box; - min-width: 170px; - margin: 10px 0; -` - -export const GithubButton = styled(ExternalButton)` - i { - background-image: url(/img/github_icon.svg); - } -` - -export const DiscordButton = styled(ExternalButton)` - i { - background-image: url(/img/discord.svg); - width: 1.2em; - height: 1.2em; - } -` - -export const Spacer = styled.div` - height: 65px; -` - -export const Description = styled.p` - color: #3c3937; -` diff --git a/src/components/Documentation/RightPanel/styles.module.css b/src/components/Documentation/RightPanel/styles.module.css new file mode 100644 index 0000000000..bd5eaa8986 --- /dev/null +++ b/src/components/Documentation/RightPanel/styles.module.css @@ -0,0 +1,86 @@ +.container { + position: sticky; + top: var(--layout-header-height-collapsed); + flex-shrink: 0; + width: 170px; + height: calc(100vh - 78px); + font-size: 16px; + overflow-y: auto; + + @media only screen and (max-width: 1200px) { + display: none; + } +} + +.separator { + border: none; + border-top: 1px solid var(--color-lighter-blue); +} + +.list { + margin-top: 57px; + margin-bottom: 27px; +} + +.header { + font-size: 14px; + text-transform: uppercase; + color: var(--color-black); +} + +.headingLink { + position: relative; + display: block; + min-height: 26px; + margin-bottom: 3px; + color: #a0a8a5; + text-decoration: none; + font-size: 16px; + font-weight: 400; + line-height: 26px; + cursor: pointer; + + &.current { + color: #000; + } + + &:hover { + color: #3c3937; + } +} + +.buttonSection { + & + & { + margin-top: 25px; + } +} + +.buttonSectionIcon { + display: inline-block; + width: 1em; + height: 1em; + margin-right: 5px; +} + +.buttonSectionDescription { + color: var(--color-black); +} + +.button { + width: 100%; + margin: 10px 0; + + &.tutorials { + white-space: nowrap; + } +} + +.githubIcon { + background-image: url(/img/github_icon.svg); +} + +.discordIcon { + width: 1.2em; + height: 1.2em; + background-image: url(/img/discord.svg); +} diff --git a/src/components/Documentation/Tutorials/index.js b/src/components/Documentation/Tutorials/index.js deleted file mode 100644 index 360575b70c..0000000000 --- a/src/components/Documentation/Tutorials/index.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import topairs from 'lodash.topairs' -import startCase from 'lodash.startcase' - -import { ExternalButton, KatacodaButton, Wrapper } from './styles' - -const icons = { - katacoda: KatacodaButton -} - -export default function Tutorials({ compact, tutorials }) { - const tutorialsData = topairs(tutorials) - - return ( - - {tutorialsData.map(([k, value]) => { - const ButtonComponent = icons[k] || ExternalButton - - return ( - - {icons[k] && } - {!compact && `Run in ${startCase(k)}`} - - ) - })} - - ) -} - -Tutorials.propTypes = { - compact: PropTypes.bool, - tutorials: PropTypes.object -} diff --git a/src/components/Documentation/Tutorials/styles.js b/src/components/Documentation/Tutorials/styles.js deleted file mode 100644 index 9071315bac..0000000000 --- a/src/components/Documentation/Tutorials/styles.js +++ /dev/null @@ -1,37 +0,0 @@ -import styled from 'styled-components' - -import { LightButton } from '../styles' - -export const Wrapper = styled.div`` - -export const ExternalButton = styled(LightButton)` - box-sizing: border-box; - min-height: 36px; - - ${({ compact }) => - !compact && - ` - width: 100%; - margin: 10px 0; - `} - - ${({ compact }) => - compact && - ` - margin-left: 5px; - - i { - margin-right: 0; - } - `} -` - -export const KatacodaButton = styled(ExternalButton)` - white-space: nowrap; - - i { - background-image: url(/img/katacoda_grey_small.png); - width: 24px; - height: 24px; - } -` diff --git a/src/components/Documentation/TutorialsLinks/index.tsx b/src/components/Documentation/TutorialsLinks/index.tsx new file mode 100644 index 0000000000..10f399a0e7 --- /dev/null +++ b/src/components/Documentation/TutorialsLinks/index.tsx @@ -0,0 +1,46 @@ +import React from 'react' +import cn from 'classnames' +import topairs from 'lodash.topairs' +import startCase from 'lodash.startcase' + +import Link from '../../Link' + +import sharedStyles from '../styles.module.css' +import styles from './styles.module.css' + +interface ITutorialsLinksProps { + compact?: boolean + buttonClassName?: string + tutorials: { [type: string]: string } +} + +const TutorialsLinks: React.SFC = ({ + compact = false, + buttonClassName, + tutorials +}) => ( + <> + {topairs(tutorials).map(([type, href]) => { + return ( + + {styles[`${type}Icon`] && ( + + )} + {!compact && `Run in ${startCase(type)}`} + + ) + })} + +) + +export default TutorialsLinks diff --git a/src/components/Documentation/TutorialsLinks/styles.module.css b/src/components/Documentation/TutorialsLinks/styles.module.css new file mode 100644 index 0000000000..683d32c9b4 --- /dev/null +++ b/src/components/Documentation/TutorialsLinks/styles.module.css @@ -0,0 +1,21 @@ +.button { + &:not(.compact) { + & + & { + margin-top: 10px; + } + } + + &.compact { + margin-left: 5px; + + .katacodaIcon { + margin-right: 0; + } + } +} + +.katacodaIcon { + width: 24px; + height: 24px; + background-image: url(/img/katacoda_grey_small.png); +} diff --git a/src/components/Documentation/index.js b/src/components/Documentation/index.tsx similarity index 62% rename from src/components/Documentation/index.js rename to src/components/Documentation/index.tsx index 6f724e1eb0..f5a89aac55 100644 --- a/src/components/Documentation/index.js +++ b/src/components/Documentation/index.tsx @@ -1,12 +1,26 @@ import React from 'react' -import PropTypes from 'prop-types' import Markdown from './Markdown' import RightPanel from './RightPanel' -import { getItemByPath } from '../../utils/sidebar' +import { getItemByPath } from '../../utils/shared/sidebar' -export default function Documentation({ htmlAst, path, headings }) { +export interface IHeading { + slug: string + text: string +} + +interface IDocumentationProps { + path: string + headings: Array + htmlAst: object +} + +const Documentation: React.SFC = ({ + htmlAst, + path, + headings +}) => { const { source, prev, next, tutorials } = getItemByPath(path) const githubLink = `https://github.com/iterative/dvc.org/blob/master/content${source}` @@ -28,8 +42,4 @@ export default function Documentation({ htmlAst, path, headings }) { ) } -Documentation.propTypes = { - path: PropTypes.string, - headings: PropTypes.array, - htmlAst: PropTypes.object -} +export default Documentation diff --git a/src/components/Documentation/styles.js b/src/components/Documentation/styles.js deleted file mode 100644 index 37bd1b821d..0000000000 --- a/src/components/Documentation/styles.js +++ /dev/null @@ -1,31 +0,0 @@ -import styled from 'styled-components' - -export const LightButton = styled.a` - display: inline-flex; - justify-content: center; - align-items: center; - color: #575e64; - background-color: white; - border: 1px solid #575e64; - text-decoration: none; - font-weight: 500; - line-height: 30px; - padding: 2px 16px; - border-radius: 3px; - cursor: pointer; - transition: 0.2s background-color ease-out; - z-index: 1; - - &:hover { - background-color: #f5f7f9; - } - - i { - background-size: contain; - background-repeat: no-repeat; - background-position: center; - width: 1em; - height: 1em; - margin-right: 7px; - } -` diff --git a/src/components/Documentation/styles.module.css b/src/components/Documentation/styles.module.css new file mode 100644 index 0000000000..666d6d6817 --- /dev/null +++ b/src/components/Documentation/styles.module.css @@ -0,0 +1,33 @@ +.button { + position: relative; + z-index: 1; + box-sizing: border-box; + display: inline-flex; + justify-content: center; + align-items: center; + min-height: 36px; + padding: 2px 16px; + border: 1px solid var(--color-gray-dark); + border-radius: 3px; + text-decoration: none; + font-weight: 500; + line-height: 30px; + background-color: white; + color: var(--color-gray-dark); + cursor: pointer; + transition: 0.2s background-color ease-out; + + &:hover { + background-color: var(--color-light-blue); + } +} + +.buttonIcon { + flex-shrink: 0; + width: 1em; + height: 1em; + margin-right: 7px; + background-size: contain; + background-repeat: no-repeat; + background-position: center; +} diff --git a/src/components/DownloadButton/index.js b/src/components/DownloadButton/index.js deleted file mode 100644 index e7b93691b9..0000000000 --- a/src/components/DownloadButton/index.js +++ /dev/null @@ -1,199 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' - -import isClient from '../../utils/isClient' -import { logEvent } from '../../utils/ga' - -import Link from '../Link' - -import { - Action, - Button, - Delimiter, - Description, - DownloadInput, - DownloadLink, - Handler, - Icon, - Inner, - Links, - Popup, - Triangle -} from './styles' - -const VERSION = `0.92.0` -const OSX = `osx` -const WINDOWS = `win` -const LINUX = `linux` -const LINUX_RPM = `linux_rpm` -const UNKNOWN = `...` -const LINE = `line` - -const links = { - [OSX]: { - title: 'Mac OS', - url: `https://github.com/iterative/dvc/releases/download/${VERSION}/dvc-${VERSION}.pkg`, - download: true - }, - [WINDOWS]: { - title: 'Windows', - url: `https://github.com/iterative/dvc/releases/download/${VERSION}/dvc-${VERSION}.exe`, - download: true - }, - [LINUX]: { - title: 'Linux Deb', - url: `https://github.com/iterative/dvc/releases/download/${VERSION}/dvc_${VERSION}_amd64.deb`, - download: true - }, - [LINUX_RPM]: { - title: 'Linux RPM', - url: `https://github.com/iterative/dvc/releases/download/${VERSION}/dvc-${VERSION}-1.x86_64.rpm`, - download: true - }, - [LINE]: { - line: true - }, - [UNKNOWN]: { - title: 'pip, conda, brew', - url: `/doc/install`, - download: false - } -} - -export default class DownloadButton extends Component { - state = { - os: UNKNOWN, - open: false, - clicked: false - } - - constructor() { - super() - - this.handleClickOutside = this.handleClickOutside.bind(this) - } - - componentDidMount() { - const os = this.getSystemOS() - this.setState({ os }) - - document.addEventListener('mousedown', this.handleClickOutside) - } - - componentWillUnmount() { - document.removeEventListener('mousedown', this.handleClickOutside) - } - - setRef = ref => (this.ref = ref) - - handleClickOutside = event => { - if (this.ref && !this.ref.contains(event.target)) { - if (this.state.open) { - this.close() - } - } - } - - getSystemOS = () => { - let OSName = UNKNOWN - if (!isClient) return OSName - - if (navigator.userAgent.indexOf('Win') !== -1) OSName = WINDOWS - if (navigator.userAgent.indexOf('Mac') !== -1) OSName = OSX - if (navigator.userAgent.indexOf('Linux') !== -1) OSName = LINUX - - return OSName - } - - close = () => this.setState({ open: false }) - - toggle = () => { - if (!this.state.clicked) { - logEvent('button', 'download') - } - this.setState(prevState => ({ - open: !prevState.open, - clicked: true - })) - } - - download = id => { - this.close() - logEvent('download', id) - } - - renderLinks = () => ( - - {[UNKNOWN, LINE, OSX, WINDOWS, LINUX, LINUX_RPM].map(id => { - const link = links[id] - - if (link.line) { - return - } - - if (!link.url) { - return ( - - ) - } - - return ( - this.download(id)} - active={id === this.state.os} - > - {link.title} - - ) - })} - - ) - - render() { - const { openTop } = this.props - const { os, open } = this.state - const currentOS = links[os] - - return ( - - - {open && {this.renderLinks()}} - - ) - } -} - -DownloadButton.propTypes = { - openTop: PropTypes.bool -} diff --git a/src/components/DownloadButton/index.tsx b/src/components/DownloadButton/index.tsx new file mode 100644 index 0000000000..021ef82dae --- /dev/null +++ b/src/components/DownloadButton/index.tsx @@ -0,0 +1,181 @@ +import React, { useRef, useEffect, useCallback, useState } from 'react' +import cn from 'classnames' + +import TwoRowsButton from '../TwoRowsButton' +import Link from '../Link' + +import isClient from '../../utils/front/isClient' +import { logEvent } from '../../utils/front/ga' + +import styles from './styles.module.css' + +const VERSION = `0.92.0` + +enum OS { + UNKNOWN = '...', + OSX = 'osx', + WINDOWS = 'win', + LINUX = 'linux', + LINUX_RPM = 'linux_rpm' +} + +const itemsByOs = { + [OS.UNKNOWN]: { + title: 'pip, conda, brew', + url: `/doc/install`, + download: false + }, + [OS.OSX]: { + title: 'Mac OS', + url: `https://github.com/iterative/dvc/releases/download/${VERSION}/dvc-${VERSION}.pkg`, + download: true + }, + [OS.WINDOWS]: { + title: 'Windows', + url: `https://github.com/iterative/dvc/releases/download/${VERSION}/dvc-${VERSION}.exe`, + download: true + }, + [OS.LINUX]: { + title: 'Linux Deb', + url: `https://github.com/iterative/dvc/releases/download/${VERSION}/dvc_${VERSION}_amd64.deb`, + download: true + }, + [OS.LINUX_RPM]: { + title: 'Linux RPM', + url: `https://github.com/iterative/dvc/releases/download/${VERSION}/dvc-${VERSION}-1.x86_64.rpm`, + download: true + } +} +const dropdownItems = [ + OS.UNKNOWN, + null, + OS.OSX, + OS.WINDOWS, + OS.LINUX, + OS.LINUX_RPM +] + +interface IDownloadButtonDropdownItemsProps { + userOS: OS + onClick: (os: OS) => void +} + +interface IDownloadButtonProps { + openTop?: boolean +} + +const userAgentIs = (value: string): boolean => + navigator.userAgent.indexOf(value) !== -1 + +const getUserOS = (): OS => { + let OSName = OS.UNKNOWN + + if (!isClient) return OSName + if (userAgentIs('Win')) OSName = OS.WINDOWS + if (userAgentIs('Mac')) OSName = OS.OSX + if (userAgentIs('Linux')) OSName = OS.LINUX + + return OSName +} + +const DownloadButtonDropdownItems: React.SFC = ({ + onClick, + userOS +}) => { + return ( +
+ {dropdownItems.map((os, index) => { + if (os === null) { + return ( +
+ ) + } + + const item = itemsByOs[os] + + return ( + onClick(os)} + > + {item.title} + + ) + })} +
+ ) +} + +const DownloadButton: React.SFC = ({ openTop }) => { + const userOS = useRef(getUserOS()) + const containerRef = useRef(null) + const [isOpened, setOpened] = useState(false) + const [isClicked, setClicked] = useState(false) + const currentOS = itemsByOs[userOS.current] + const toggle = useCallback( + () => + setOpened(prev => { + if (!isClicked) { + setClicked(true) + logEvent('button', 'download') + } + + return !prev + }), + [isOpened, isClicked] + ) + const download = (os: OS): void => { + setOpened(false) + logEvent('download', os) + } + + useEffect(() => { + const onOutsideClick = (e: MouseEvent): void => { + if (isOpened && !containerRef.current?.contains(e.target as Node)) { + setOpened(false) + } + } + + document.addEventListener('mousedown', onOutsideClick) + + return (): void => document.removeEventListener('mousedown', onOutsideClick) + }, [isOpened, containerRef.current]) + + return ( + + + } + onClick={toggle} + > + + + {isOpened && ( +
+ +
+ )} +
+ ) +} + +export default DownloadButton diff --git a/src/components/DownloadButton/styles.js b/src/components/DownloadButton/styles.js deleted file mode 100644 index 9659166e97..0000000000 --- a/src/components/DownloadButton/styles.js +++ /dev/null @@ -1,144 +0,0 @@ -import styled, { css } from 'styled-components' - -export const Handler = styled.span` - position: relative; - display: inline-block; - width: 186px; - height: 60px; -` - -export const Button = styled.button` - position: relative; - width: 186px; - height: 60px; - border: none; - border-radius: 4px; - background-color: #945dd6; - - padding: 0px; - color: #ffffff; - - cursor: pointer; - z-index: 9; - - display: flex; - flex-direction: row; - align-items: center; - transition: 0.2s background-color ease-out; - - ${props => - props.open && - ` - background-color: #885CCB; - `} &:hover { - background-color: #885ccb; - } -` - -export const Icon = styled.div` - flex-basis: 48px; - - text-align: center; -` - -export const Inner = styled.div` - display: flex; - justify-content: space-between; - flex: 1; -` - -export const Action = styled.h6` - font-family: BrandonGrotesque; - font-weight: 500; - font-size: 20px; - line-height: 0.9; -` - -export const Description = styled.p` - font-family: BrandonGrotesque; - font-size: 14px; - text-align: left; -` - -export const Triangle = styled.div` - margin-right: 19px; - align-items: center; - display: flex; - - transition: left 300ms linear; - - ${props => - props.open && - ` - transition: left 300ms linear; - transform: rotate(-180deg); - `}; -` - -export const Popup = styled.div` - position: absolute; - left: 0px; - right: 0px; - top: calc(100% + 3px); - background-color: #ffffff; - box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.15); - - ${props => - props.openTop && - ` - bottom: calc(100% + 3px); - top: auto; - `}; -` - -export const Links = styled.div` - display: flex; - flex-direction: column; -` - -const Item = css` - font-family: BrandonGrotesque; - display: block; - min-height: 36px; - line-height: 1.29; - padding: 0px 17px; - - display: flex; - align-items: center; - text-decoration: none; - - color: #b0b8c5; -` - -export const Delimiter = styled.div` - background-color: rgba(0, 0, 0, 0.1); - height: 1px; -` - -export const DownloadInput = styled.input` - ${Item}; - border: none !important; - font-family: Monospace; - font-weight: bold; - - ${props => - props.active && - ` - color: #40364d; - `}; -` - -export const DownloadLink = styled.a` - ${Item}; - color: #b0b8c5; - - &:hover { - color: #40364d; - } - - ${props => - props.active && - ` - color: #40364d; - `}; -` diff --git a/src/components/DownloadButton/styles.module.css b/src/components/DownloadButton/styles.module.css new file mode 100644 index 0000000000..2f389194f7 --- /dev/null +++ b/src/components/DownloadButton/styles.module.css @@ -0,0 +1,68 @@ +.container { + position: relative; + display: inline-block; +} + +.button.button { + padding-right: 55px; +} + +.buttonIcon { + position: relative; + top: -2px; + width: 14px; + height: 20px; +} + +.triangle { + position: absolute; + right: 15px; + align-items: center; + display: flex; + transition: left 300ms linear; + + .button.opened & { + transition: left 300ms linear; + transform: rotate(-180deg); + } +} + +.dropdown { + position: absolute; + left: 0; + right: 0; + top: calc(100% + 3px); + display: flex; + flex-direction: column; + background-color: #fff; + box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.15); + + &.openTop { + bottom: calc(100% + 3px); + top: auto; + } +} + +.dropdownItem { + font-family: var(--font-brandon); + min-height: 36px; + line-height: 1.29; + padding: 0 17px; + display: flex; + align-items: center; + text-decoration: none; + color: var(--color-gray-light); + + &.active { + color: var(--color-gray-hover); + } + + &:hover { + color: var(--color-gray-hover); + } +} + +.dropdownDelimiter { + background-color: rgba(0, 0, 0, 0.1); + height: 1px; +} diff --git a/src/components/Features/index.js b/src/components/Features/index.js deleted file mode 100644 index b762bbebca..0000000000 --- a/src/components/Features/index.js +++ /dev/null @@ -1,156 +0,0 @@ -import React from 'react' - -import Hero from '../Hero' -import FeaturesHero from '../FeaturesHero' -import TrySection from '../TrySection' - -import { Container, Description, Feature, Features, Icon, Name } from './styles' - -export default function FeaturesPage() { - return ( - <> - - - - - - - - Git compatible - - Git-compatible - - DVC runs on top of any Git repository and is compatible with any - standard Git server or provider (GitHub, GitLab, etc). Data file - contents can be shared by network-accessible storage or any - supported cloud solution. DVC offers all the advantages of a - distributed version control system — lock-free, local branching, - and versioning. - - - - - Storage agnostic - - Storage agnostic - - Use Amazon S3, Microsoft Azure Blob Storage, Google Drive, Google - Cloud Storage, Aliyun OSS, SSH/SFTP, HDFS, HTTP, network-attached - storage, or disc to store data. The list of supported remote - storage is constantly expanding. - - - - - Reproducibility - - Reproducible - - The single 'dvc repro' command reproduces experiments - end-to-end. DVC guarantees reproducibility by consistently - maintaining a combination of input data, configuration, and the - code that was initially used to run an experiment. - - - - - Low-friction branching - - Low friction branching - - DVC fully supports instantaneous Git branching, even with large - files. Branches beautifully reflect the non-linear structure and - highly iterative nature of a ML process. Data is not duplicated — - one file version can belong to dozens of experiments. Create as - many experiments as you want, instantaneously switch back and - forth, and save a history of all attempts. - - - - - - - Metric tracking - - Metrics are first-class citizens in DVC. DVC includes a command to - list all branches, along with metric values, to track the progress - or pick the best version. - - - - - ML pipelines framework - - ML pipeline framework - - DVC has a built-in way to connect ML steps into a DAG and run the - full pipeline end-to-end. DVC handles caching of intermediate - results and does not run a step again if input data or code are - the same. - - - - - Language & framework agnostic - - Language- & framework-agnostic - - No matter which programming language or libraries are in use or - how code is structured, reproducibility and pipelines are based on - input and output files or directories. Python, R, Julia, Scala - Spark, custom binary, Notebooks, flatfiles/TensorFlow, PyTorch, - etc. are all supported. - - - - - HDFS, Hive & Apache Spark - - HDFS, Hive & Apache Spark - - Include Spark and Hive jobs in the DVC data versioning cycle along - with local ML modeling steps or manage Spark and Hive jobs with - DVC end-to-end. Drastically decrease a feedback loop by - decomposing a heavy cluster job into smaller DVC pipeline steps. - Iterate on the steps independently with respect to dependencies. - - - - - Failure tracking - - Track failures - - Bad ideas can sometimes spark more ideas among colleagues than - successful ones. Retaining knowledge of failed attempts can save - time in the future. DVC is built to track everything in a - reproducible and easily accessible way. - - - - - - - ) -} diff --git a/src/components/Features/index.tsx b/src/components/Features/index.tsx new file mode 100644 index 0000000000..2d16367c3b --- /dev/null +++ b/src/components/Features/index.tsx @@ -0,0 +1,169 @@ +import React from 'react' + +import PageContent from '../PageContent' +import LayoutWidthContainer from '../LayoutWidthContainer' +import HeroSection from '../HeroSection' +import Link from '../Link' +import PromoSection from '../PromoSection' + +import styles from './styles.module.css' + +const FeaturesPage: React.SFC = () => ( + <> + + +

+ DVC brings agility, reproducibility, and collaboration into your + existing data science workflow +

+
+ +
+
+ Git compatible +

Git-compatible

+
+ DVC runs on top of any Git repository and is compatible with any + standard Git server or provider (GitHub, GitLab, etc). Data file + contents can be shared by network-accessible storage or any + supported cloud solution. DVC offers all the advantages of a + distributed version control system — lock-free, local branching, + and versioning. +
+
+
+ Storage agnostic +

Storage agnostic

+
+ Use Amazon S3, Microsoft Azure Blob Storage, Google Drive, Google + Cloud Storage, Aliyun OSS, SSH/SFTP, HDFS, HTTP, network-attached + storage, or disc to store data. The list of supported remote + storage is constantly expanding. +
+
+
+ Reproducibility +

Reproducible

+
+ The single 'dvc repro' command reproduces experiments + end-to-end. DVC guarantees reproducibility by consistently + maintaining a combination of input data, configuration, and the + code that was initially used to run an experiment. +
+
+
+ Low-friction branching +

Low friction branching

+
+ DVC fully supports instantaneous Git branching, even with large + files. Branches beautifully reflect the non-linear structure and + highly iterative nature of a ML process. Data is not duplicated — + one file version can belong to dozens of experiments. Create as + many experiments as you want, instantaneously switch back and + forth, and save a history of all attempts. +
+
+
+ +

Metric tracking

+
+ Metrics are first-class citizens in DVC. DVC includes a command to + list all branches, along with metric values, to track the progress + or pick the best version. +
+
+
+ ML pipelines framework +

ML pipeline framework

+
+ DVC has a built-in way to connect ML steps into a DAG and run the + full pipeline end-to-end. DVC handles caching of intermediate + results and does not run a step again if input data or code are + the same. +
+
+
+ Language & framework agnostic +

+ Language- & framework-agnostic +

+
+ No matter which programming language or libraries are in use or + how code is structured, reproducibility and pipelines are based on + input and output files or directories. Python, R, Julia, Scala + Spark, custom binary, Notebooks, flatfiles/TensorFlow, PyTorch, + etc. are all supported. +
+
+
+ HDFS, Hive & Apache Spark +

HDFS, Hive & Apache Spark

+
+ Include Spark and Hive jobs in the DVC data versioning cycle along + with local ML modeling steps or manage Spark and Hive jobs with + DVC end-to-end. Drastically decrease a feedback loop by + decomposing a heavy cluster job into smaller DVC pipeline steps. + Iterate on the steps independently with respect to dependencies. +
+
+
+ Failure tracking +

Track failures

+
+ Bad ideas can sometimes spark more ideas among colleagues than + successful ones. Retaining knowledge of failed attempts can save + time in the future. DVC is built to track everything in a + reproducible and easily accessible way. +
+
+
+
+
+ + Get Started + + ]} + /> + +) + +export default FeaturesPage diff --git a/src/components/Features/styles.js b/src/components/Features/styles.js deleted file mode 100644 index c2133c1e24..0000000000 --- a/src/components/Features/styles.js +++ /dev/null @@ -1,54 +0,0 @@ -import styled from 'styled-components' - -import { container, media } from '../../styles' - -export const Container = styled.div` - ${container}; -` - -export const Features = styled.div` - display: flex; - flex-flow: row; - flex-wrap: wrap; - padding-top: 110px; - padding-bottom: 90px; - - ${media.phablet` - padding-top: 70px; - padding-bottom: 50px; - `}; -` - -export const Feature = styled.div` - flex: 33.3%; - flex-basis: 311px; - margin-bottom: 63px; -` - -export const Icon = styled.div` - height: 48px; - - img { - width: 48px; - height: 48px; - } -` - -export const Name = styled.h3` - font-family: BrandonGrotesque; - margin-top: 10px; - margin-bottom: 10px; - - font-size: 20px; - font-weight: 500; - color: #40364d; - - min-height: 28px; -` - -export const Description = styled.div` - max-width: 311px; - - font-size: 16px; - color: #5f6c72; -` diff --git a/src/components/Features/styles.module.css b/src/components/Features/styles.module.css new file mode 100644 index 0000000000..1e15de0718 --- /dev/null +++ b/src/components/Features/styles.module.css @@ -0,0 +1,58 @@ +.heroContainer { + padding-top: 70px; + padding-bottom: 70px; + overflow: hidden; +} + +.heroHeading { + max-width: 610px; + min-height: 185px; + margin: 0 auto; + font-family: var(--font-brandon); + font-size: 40px; + font-weight: 500; + line-height: 1.4; + text-align: center; + color: var(--color-gray-hover); +} + +.features { + display: flex; + flex-flow: row; + flex-wrap: wrap; + padding-top: 110px; + margin: 0 -10px; + + @media (--xs-scr) { + padding-top: 70px; + padding-bottom: 50px; + } +} + +.feature { + flex: 33.3%; + flex-basis: 311px; + padding: 0 10px; + margin-bottom: 63px; +} + +.featureIcon { + display: block; + width: 48px; + height: 48px; +} + +.featureName { + min-height: 28px; + margin-top: 10px; + margin-bottom: 10px; + font-family: var(--font-brandon); + font-size: 20px; + font-weight: 500; + color: var(--color-gray-hover); +} + +.featureDescription { + font-size: 16px; + color: var(--color-gray-dark); +} diff --git a/src/components/FeaturesHero/index.js b/src/components/FeaturesHero/index.js deleted file mode 100644 index 45dc328401..0000000000 --- a/src/components/FeaturesHero/index.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react' - -import { Heading, Wrapper } from './styles' - -export default function FeaturesHero() { - return ( - - - DVC brings agility, reproducibility, and collaboration into your - existing data science workflow - - - ) -} diff --git a/src/components/FeaturesHero/styles.js b/src/components/FeaturesHero/styles.js deleted file mode 100644 index 0217fad826..0000000000 --- a/src/components/FeaturesHero/styles.js +++ /dev/null @@ -1,19 +0,0 @@ -import styled from 'styled-components' - -export const Wrapper = styled.div` - padding-top: 70px; - padding-bottom: 70px; - overflow: hidden; -` - -export const Heading = styled.h1` - font-family: BrandonGrotesque; - margin: 0px auto; - max-width: 610px; - min-height: 185px; - font-size: 40px; - font-weight: 500; - line-height: 1.4; - text-align: center; - color: #40364d; -` diff --git a/src/components/GithubLine/index.js b/src/components/GithubLine/index.js deleted file mode 100644 index f9ca7dddaa..0000000000 --- a/src/components/GithubLine/index.js +++ /dev/null @@ -1,30 +0,0 @@ -import React, { useEffect, useState } from 'react' -import Link from '../Link' - -import { Count, Github, Link as LinkSC, Star, Wrapper } from './styles' - -const repo = `iterative/dvc` -const gh = `https://github.com/${repo}` -const api = `https://api.github.com/repos/${repo}` - -export default function GithubLine() { - const [count, setCount] = useState('–––') - - useEffect(() => { - fetch(api).then(res => { - res.json().then(data => setCount(data.stargazers_count)) - }) - }, [count, setCount]) - - return ( - - - We’re on - - GitHub - - {' '} - {count} - - ) -} diff --git a/src/components/GithubLine/styles.js b/src/components/GithubLine/styles.js deleted file mode 100644 index cc09e0e8d1..0000000000 --- a/src/components/GithubLine/styles.js +++ /dev/null @@ -1,39 +0,0 @@ -import styled from 'styled-components' - -export const Wrapper = styled.div` - font-family: BrandonGrotesque; - font-weight: 500; - line-height: 20px; - height: 20px; - display: flex; - align-items: center; -` - -export const Link = styled.a` - font-family: BrandonGrotesque; - font-weight: 500; - color: #40364d; - margin-left: 0.3em; - - &:focus, - &:hover, - &:visited { - color: #40364d; - } -` - -export const Github = styled.img` - font-family: BrandonGrotesque; - font-weight: 500; - margin-right: 9px; -` - -export const Star = styled.img` - margin-left: 7px; -` - -export const Count = styled.span` - font-family: BrandonGrotesque; - font-weight: 500; - margin-left: 6.3px; -` diff --git a/src/components/Hamburger/index.tsx b/src/components/Hamburger/index.tsx deleted file mode 100644 index edb16148f3..0000000000 --- a/src/components/Hamburger/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import cn from 'classnames' -import React from 'react' - -import styles from './styles.module.css' - -interface IHamburgetProps { - opened: boolean -} - -export default function Hamburger({ opened }: IHamburgetProps) { - return ( -
-
-
-
-
- ) -} diff --git a/src/components/HamburgerIcon/index.tsx b/src/components/HamburgerIcon/index.tsx new file mode 100644 index 0000000000..077cc31a1b --- /dev/null +++ b/src/components/HamburgerIcon/index.tsx @@ -0,0 +1,18 @@ +import cn from 'classnames' +import React from 'react' + +import styles from './styles.module.css' + +interface IHamburgetProps { + opened?: boolean +} + +const HamburgerIcon: React.SFC = ({ opened }) => ( +
+
+
+
+
+) + +export default HamburgerIcon diff --git a/src/components/Hamburger/styles.module.css b/src/components/HamburgerIcon/styles.module.css similarity index 100% rename from src/components/Hamburger/styles.module.css rename to src/components/HamburgerIcon/styles.module.css diff --git a/src/components/HamburgerMenu/index.tsx b/src/components/HamburgerMenu/index.tsx index 1cf412660c..c954153b7e 100644 --- a/src/components/HamburgerMenu/index.tsx +++ b/src/components/HamburgerMenu/index.tsx @@ -1,18 +1,18 @@ import cn from 'classnames' import React, { useCallback, useState, useEffect } from 'react' -import Hamburger from '../Hamburger' +import HamburgerIcon from '../HamburgerIcon' import Link from '../Link' -import { logEvent } from '../../utils/ga' -import { getFirstPage } from '../../utils/sidebar' +import { logEvent } from '../../utils/front/ga' +import { getFirstPage } from '../../utils/shared/sidebar' import { ReactComponent as LogoSVG } from '../../../static/img/logo-white.svg' import styles from './styles.module.css' const docsPage = getFirstPage() -function HamburgerMenu() { +const HamburgerMenu: React.SFC = () => { const [isOpened, setOpened] = useState(false) const toggleMobileMenu = useCallback(() => setOpened(!isOpened), [isOpened]) @@ -24,7 +24,7 @@ function HamburgerMenu() { const close = useCallback(() => setOpened(false), [isOpened]) const itemClick = useCallback( - item => () => { + item => (): void => { close() logEvent('hamburger', item) }, @@ -34,7 +34,7 @@ function HamburgerMenu() { useEffect(() => { const method = isOpened ? 'add' : 'remove' - document.body.classList[method]('noScroll') + document.body.classList[method](styles.hiddenScrollbar) }, [isOpened]) return ( @@ -44,7 +44,7 @@ function HamburgerMenu() { onClick={toggleMobileMenu} onKeyDown={openOnEnterKey} > - +
diff --git a/src/components/HamburgerMenu/styles.module.css b/src/components/HamburgerMenu/styles.module.css index 705e6c6c54..91ebfa6f54 100644 --- a/src/components/HamburgerMenu/styles.module.css +++ b/src/components/HamburgerMenu/styles.module.css @@ -1,3 +1,7 @@ +.hiddenScrollbar { + overflow-y: hidden; +} + .wrapper { display: block; padding: 25px 15px 15px; diff --git a/src/components/Hero/index.js b/src/components/Hero/index.js deleted file mode 100644 index ba7ab7d285..0000000000 --- a/src/components/Hero/index.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -import { Container, Wrapper } from './styles' - -export default function Hero({ children }) { - return ( - - {children} - - ) -} - -Hero.propTypes = { - children: PropTypes.node.isRequired -} diff --git a/src/components/Hero/styles.js b/src/components/Hero/styles.js deleted file mode 100644 index 7751cffcd1..0000000000 --- a/src/components/Hero/styles.js +++ /dev/null @@ -1,12 +0,0 @@ -import styled from 'styled-components' - -import { container } from '../../styles' - -export const Wrapper = styled.section` - position: relative; - background-color: #eef4f8; -` - -export const Container = styled.div` - ${container}; -` diff --git a/src/components/HeroSection/index.tsx b/src/components/HeroSection/index.tsx new file mode 100644 index 0000000000..fb672b5417 --- /dev/null +++ b/src/components/HeroSection/index.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import cn from 'classnames' + +import LayoutWidthContainer from '../LayoutWidthContainer' + +import styles from './styles.module.css' + +interface IHeroSectionProps { + className?: string + children: React.ReactNode +} + +const HeroSection: React.SFC = ({ className, children }) => ( +
+ {children} +
+) + +export default HeroSection diff --git a/src/components/HeroSection/styles.module.css b/src/components/HeroSection/styles.module.css new file mode 100644 index 0000000000..31f10d75f0 --- /dev/null +++ b/src/components/HeroSection/styles.module.css @@ -0,0 +1,3 @@ +.heroSection { + background-color: var(--color-light-blue); +} diff --git a/src/components/Home/Diagram/index.tsx b/src/components/Home/Diagram/index.tsx new file mode 100644 index 0000000000..83a21d46ce --- /dev/null +++ b/src/components/Home/Diagram/index.tsx @@ -0,0 +1,161 @@ +import React, { forwardRef } from 'react' +import Slider from 'react-slick' +import cn from 'classnames' + +import LayoutWidthContainer from '../../LayoutWidthContainer' +import ShowOnly from '../../ShowOnly' +import Link from '../../Link' + +import 'slick-carousel/slick/slick.css' +import 'slick-carousel/slick/slick-theme.css' +import styles from './styles.module.css' + +const LearnMore: React.SFC<{ href: string }> = ({ href }) => ( +
+ + Learn more + + +
+) + +const ColumnOne: React.SFC = () => ( +
+

+ ML project version control +

+
+

+ Version control machine learning models, data sets and intermediate + files. DVC connects them with code, and uses Amazon S3, Microsoft Azure + Blob Storage, Google Drive, Google Cloud Storage, Aliyun OSS, SSH/SFTP, + HDFS, HTTP, network-attached storage, or disc to store file contents. +

+

+ Full code and data provenance help track the complete evolution of every + ML model. This guarantees reproducibility and makes it easy to switch + back and forth between experiments. +

+
+ +
+) + +const ColumnTwo: React.SFC = () => ( +
+

+ ML experiment management +

+
+

+ Harness the full power of Git branches to try different ideas instead of + sloppy file suffixes and comments in code. Use automatic metric-tracking + to navigate instead of paper and pencil. +

+

+ DVC was designed to keep branching as simple and fast as in Git — no + matter the data file size. Along with first-class citizen metrics and ML + pipelines, it means that a project has cleaner structure. It's easy + to compare ideas and pick the best. Iterations become faster with + intermediate artifact caching. +

+
+ +
+) + +const ColumnThree: React.SFC = () => ( +
+

+ Deployment & Collaboration +

+
+

+ Instead of ad-hoc scripts, use push/pull commands to move consistent + bundles of ML models, data, and code into production, remote machines, + or a colleague's computer. +

+

+ DVC introduces lightweight pipelines as a first-class citizen mechanism + in Git. They are language-agnostic and connect multiple steps into a + DAG. These pipelines are used to remove friction from getting code into + production. +

+
+ +
+) + +const DiagramSection: React.ForwardRefRenderFunction = ( + _, + ref +) => ( +
+ +

DVC tracks ML models and data sets

+

+ DVC is built to make ML models shareable and reproducible. It is + designed to handle large files, data sets, machine learning models, and + metrics as well as code. +

+ + +
+ +
+
+ + + +
+
+ + +
+ ( +
    {dots}
+ )} + > +
+ ML project version control + +
+
+ ML experiment management + +
+
+ Deployment & Collaboration + +
+
+
+
+
+
+) + +export default forwardRef(DiagramSection) diff --git a/src/components/Home/Diagram/styles.module.css b/src/components/Home/Diagram/styles.module.css new file mode 100644 index 0000000000..80533d1870 --- /dev/null +++ b/src/components/Home/Diagram/styles.module.css @@ -0,0 +1,196 @@ +.diagramSection { + padding-top: 80px; + padding-bottom: 91px; +} + +.title { + @mixin h2-desktop; + + max-width: 550px; + min-height: 44px; + margin: 0 auto; + font-family: var(--font-brandon); + line-height: 45px; + text-align: center; + color: var(--color-gray-hover); +} + +.description { + @mixin text-secondary; + + max-width: 590px; + min-height: 50px; + margin: 0 auto; + padding-top: 10px; + text-align: center; + color: var(--color-gray-dark); +} + +.desktopChartContainer { + width: 100%; + margin-top: 49px; + + @media (--md-scr) { + overflow-x: scroll; + overflow-y: hidden; + } +} + +.desktopChart { + width: 100%; + max-width: 900px; + max-height: 445px; +} + +.columns { + @mixin columns; + + margin-top: 10px; + flex-direction: row; + justify-content: center; + align-items: flex-start; + + @media (--sm-scr) { + flex-direction: column; + } +} + +.column { + @mixin column; + + box-sizing: border-box; + display: block; + max-width: 33.3%; + margin-top: 49px; + padding: 0 10px; + + @media (--sm-scr) { + margin-right: 0; + flex-basis: auto; + max-width: 100%; + } + + @media (--xs-scr) { + margin-top: 20px; + flex-basis: auto; + max-width: 100%; + } +} + +.columnCaption { + margin-bottom: 12px; + font-family: var(--font-brandon); + font-size: 20px; + font-weight: 500; + + &.purple { + color: var(--color-purple); + } + + &.azure { + color: var(--color-azure); + } + + &.orange { + color: var(--color-orange-bright); + } +} + +.columnDescriptionContainer { + max-width: 100%; + font-size: 16px; + color: var(--color-gray-dark); +} + +.columnDescription { + margin-bottom: 24px; + + @media (--sm-scr) { + margin-bottom: 12px; + } +} + +.learnMoreContainer { + font-family: var(--font-brandon); + line-height: 28px; + font-size: 20px; + font-weight: 500; + color: var(--color-purple); +} + +.learnMoreLink { + display: flex; + align-items: center; + text-decoration: none; + color: var(--color-purple); + + &:hover { + color: var(--color-purple-hover); + } + + &:visited { + color: var(--color-purple); + } + + &:visited:hover { + color: var(--color-purple-hover); + } +} + +.learnMoreLinkIcon { + width: 18px; + height: 18px; + margin-left: 19px; + margin-top: 3px; +} + +.sliderContainer { + :global { + .slick-next, + .slick-prev { + height: 30px; + width: 30px; + z-index: 3; + } + + .slick-next { + right: -25px; + } + + .slick-prev { + left: -25px; + } + + .slick-next::before, + .slick-prev::before { + font-size: 30px; + line-height: 1; + opacity: 0.35; + color: #40364d; + } + + img { + pointer-events: none; + } + } +} + +.slide { + width: 100%; +} + +.slideImage { + padding-top: 20px; + padding-bottom: 20px; + width: 100%; + max-width: 380px; + margin: 0 auto; +} + +.sliderDots { + margin-bottom: -20px; + + li button::before { + font-size: 8px; + } +} diff --git a/src/components/Home/LandingHero/GithubLine/index.tsx b/src/components/Home/LandingHero/GithubLine/index.tsx new file mode 100644 index 0000000000..6280833ff1 --- /dev/null +++ b/src/components/Home/LandingHero/GithubLine/index.tsx @@ -0,0 +1,33 @@ +import React, { useEffect, useState } from 'react' +import fetch from 'isomorphic-fetch' +import Link from '../../../Link' + +import styles from './styles.module.css' + +const repo = `iterative/dvc` +const gh = `https://github.com/${repo}` +const api = `https://api.github.com/repos/${repo}` + +const GithubLine: React.SFC = () => { + const [count, setCount] = useState('–––') + + useEffect(() => { + fetch(api) + .then(res => res.json()) + .then(data => setCount(data.stargazers_count)) + }, [count, setCount]) + + return ( +
+ + We’re on + + GitHub + + + {count} +
+ ) +} + +export default GithubLine diff --git a/src/components/Home/LandingHero/GithubLine/styles.module.css b/src/components/Home/LandingHero/GithubLine/styles.module.css new file mode 100644 index 0000000000..4638428491 --- /dev/null +++ b/src/components/Home/LandingHero/GithubLine/styles.module.css @@ -0,0 +1,41 @@ +.container { + display: flex; + align-items: center; + font-family: var(--font-brandon); + font-weight: 500; + line-height: 20px; +} + +.link { + margin-left: 0.3em; + font-family: var(--font-brandon); + font-weight: 500; + color: var(--color-gray-hover); + + &:focus, + &:hover, + &:visited { + color: var(--color-gray-hover); + } +} + +.githubLogo { + width: 20px; + height: 20px; + margin-right: 9px; + font-family: var(--font-brandon); + font-weight: 500; +} + +.starIcon { + width: 11px; + height: 11px; + margin-top: -1px; + margin-left: 7px; +} + +.count { + margin-left: 7px; + font-family: var(--font-brandon); + font-weight: 500; +} diff --git a/src/components/Home/LandingHero/index.tsx b/src/components/Home/LandingHero/index.tsx new file mode 100644 index 0000000000..f0d5aebcc9 --- /dev/null +++ b/src/components/Home/LandingHero/index.tsx @@ -0,0 +1,114 @@ +import React, { useEffect, useCallback, useState } from 'react' +import cn from 'classnames' + +import ShowOnly from '../../ShowOnly' +import Link from '../../Link' +import DownloadButton from '../../DownloadButton' +import TwoRowsButton from '../../TwoRowsButton' +import GithubLine from './GithubLine' +import { scrollIntoLayout, ease } from '../../../utils/front/scroll' +import { logEvent } from '../../../utils/front/ga' + +import styles from './styles.module.css' + +interface ILandingHeroProps { + scrollToRef: React.RefObject +} + +const LandingHero: React.SFC = ({ scrollToRef }) => { + const [activeCommand, setActiveCommand] = useState(0) + + useEffect(() => { + const interval = setInterval( + () => setActiveCommand(prev => (prev + 1) % 4), + 3000 + ) + + return (): void => clearInterval(interval) + }, []) + + const scrollToUseCases = useCallback(() => { + logEvent('button', 'how-it-works') + scrollIntoLayout(scrollToRef?.current, { + smooth: true, + duration: 800, + ease: ease.inOutCube + }) + }, [scrollToRef?.current]) + + return ( +
+
+

+ Open-source +
+ Version Control System +
+ for Machine Learning Projects +

+
+ + + Get started + + + + + + + } + onClick={scrollToUseCases} + /> +
+ +
+ +
+
+ + +
+
+ $ dvc add images +
+
+ + $ dvc run -d images -o model.p cnn.py + +
+
+ + $ dvc remote add -d myrepo s3://mybucket + +
+
+ $ dvc push +
+
+
+
+ ) +} + +export default LandingHero diff --git a/src/components/Home/LandingHero/styles.module.css b/src/components/Home/LandingHero/styles.module.css new file mode 100644 index 0000000000..43ffff9e3b --- /dev/null +++ b/src/components/Home/LandingHero/styles.module.css @@ -0,0 +1,165 @@ +.container { + display: flex; + justify-content: space-between; + padding-top: 136px; + padding-bottom: 146px; + + @media (--sm-scr) { + flex-direction: column; + padding-top: 46px; + padding-bottom: 86px; + } +} + +.about { + @media (--sm-scr) { + max-width: 412px; + width: 100%; + margin: 0 auto; + } +} + +.title { + padding-right: 2em; + font-family: var(--font-brandon); + font-size: 40px; + font-weight: 500; + color: var(--color-gray-hover); + + @media (--sm-scr) { + padding-right: 0; + font-size: 36px; + } + + @media (--xs-scr) { + padding: 0; + font-size: 32px; + } + + @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) { + padding: 0; + font-size: 34px; + } +} + +.buttonsContainer { + display: flex; + margin-top: 28px; + + @media (--xs-scr) { + flex-direction: column; + } + + @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) { + justify-content: flex-start; + } +} + +.actionButton { + min-width: 186px; + text-decoration: none; + + &.actionButton { + @media (--xs-scr) { + min-height: 60px; + margin: 0; + margin-bottom: 12px; + } + } +} + +.getStartedButton { + display: flex; + align-items: center; + padding: 0 20px; + font-size: 20px; + font-weight: 500; + line-height: 0.9; + border: solid 2px transparent; + border-radius: 4px; + color: #fff; + background-color: var(--color-azure); + transition: 0.2s background-color ease-out; + + &:hover { + background-color: var(--color-azure-hover); + } +} + +.actionButtonIcon { + width: 20px; + height: 20px; +} + +.watchVideo { + margin-left: 15px; +} + +.commands { + display: flex; + flex: 1 1 auto; + flex-direction: column; + align-items: flex-end; + max-width: 412px; + padding-top: 10px; + margin-left: 20px; + font-family: monospace; + + @media (--sm-scr) { + align-items: center; + margin: 30px auto 0; + padding-top: 24px; + } + + @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) { + align-items: center; + padding-top: 24px; + } +} + +.command { + display: flex; + align-items: center; + width: 100%; + height: 57px; + margin-bottom: 13px; + border-radius: 8px; + border: solid 1px transparent; + background-color: #fff; + color: #b4b9c4; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.08); + transform: translateZ(0); + opacity: 0.3; + transition: opacity 3s, border 0.5s, color 1s; + + &.active { + border-color: var(--color-purple); + color: var(--color-gray-hover); + opacity: 1; + } +} + +.line { + padding: 0 10px 0 12px; + font-size: 15px; + font-weight: 700; +} + +.github { + margin-top: 51px; + font-size: 14px; + font-weight: 500; + color: var(--color-gray-light); + + @media (--sm-scr) { + align-items: center; + margin-top: 24px; + font-size: 18px; + } + + @media only screen and (min-device-width: 768px) and (max-device-width: 1024px) { + align-items: center; + margin-top: 24px; + font-size: 18px; + } +} diff --git a/src/components/Home/LearnMore/index.tsx b/src/components/Home/LearnMore/index.tsx new file mode 100644 index 0000000000..d7787acfe8 --- /dev/null +++ b/src/components/Home/LearnMore/index.tsx @@ -0,0 +1,32 @@ +import React, { useCallback } from 'react' + +import { logEvent } from '../../../utils/front/ga' +import { scrollIntoLayout, ease } from '../../../utils/front/scroll' + +import styles from './styles.module.css' + +interface ILearnMoreProps { + scrollToRef: React.RefObject +} + +const LearnMore: React.SFC = ({ scrollToRef }) => { + const onClick = useCallback(() => { + logEvent('hero', 'learn-more') + scrollIntoLayout(scrollToRef?.current, { + smooth: true, + duration: 800, + ease: ease.inOutCube + }) + }, [scrollToRef?.current]) + + return ( + + ) +} + +export default LearnMore diff --git a/src/components/Home/LearnMore/styles.module.css b/src/components/Home/LearnMore/styles.module.css new file mode 100644 index 0000000000..e343436544 --- /dev/null +++ b/src/components/Home/LearnMore/styles.module.css @@ -0,0 +1,74 @@ +@keyframes bounce { + 0%, + 30%, + 45%, + 65%, + 100% { + transform: translateY(0); + } + + 40% { + transform: translateY(-13px); + } + + 60% { + transform: translateY(-5px); + } +} + +@keyframes bounceMobile { + 0%, + 30%, + 50%, + 70%, + 100% { + transform: translateY(0); + } + + 40% { + transform: translateY(-13px); + } + + 60% { + transform: translateY(-5px); + } +} + +.button { + position: absolute; + z-index: 2; + transform: translate(-50%, 0%); + left: 50%; + bottom: 16px; + display: flex; + flex-direction: column; + align-items: center; + padding: 0; + border: none; + background: none; + cursor: pointer; +} + +.icon { + width: 11px; + height: 19px; + will-change: transform; + animation: bounce 3s infinite; + + @media (--xs-scr) { + animation-name: bounceMobile; + } +} + +.caption { + font-family: var(--font-brandon); + font-size: 16px; + line-height: 23px; + font-weight: 500; + color: var(--color-gray-light); + display: initial; + + @media (--xs-scr) { + display: none; + } +} diff --git a/src/components/Home/UseCases/CollapsibleText/index.tsx b/src/components/Home/UseCases/CollapsibleText/index.tsx new file mode 100644 index 0000000000..a88caa23ce --- /dev/null +++ b/src/components/Home/UseCases/CollapsibleText/index.tsx @@ -0,0 +1,40 @@ +import React, { useState } from 'react' +import { Collapse } from 'react-collapse' + +import { isTriggeredFromKB } from '../../../../utils/front/keyboard' + +import styles from './styles.module.css' + +interface ICollapsibleTextProps { + children: React.ReactNode + header: React.ReactNode +} + +const CollapsibleText: React.SFC = ({ + header, + children +}) => { + const [isOpened, setOpened] = useState(false) + const toggle = (): void => setOpened(prev => !prev) + const toggleFromKB = (e: React.KeyboardEvent): void => { + if (isTriggeredFromKB(e)) { + toggle() + } + } + + return ( +
+ {header} + {children} + {!isOpened &&
More...
} +
+ ) +} + +export default CollapsibleText diff --git a/src/components/Home/UseCases/CollapsibleText/styles.module.css b/src/components/Home/UseCases/CollapsibleText/styles.module.css new file mode 100644 index 0000000000..c2bc48bdb0 --- /dev/null +++ b/src/components/Home/UseCases/CollapsibleText/styles.module.css @@ -0,0 +1,16 @@ +.container { + border: none; + background: none; + font-family: var(--font-brandon); + text-align: left; + appearance: none; + + :global(.ReactCollapse--collapse) { + transition: height 500ms; + } +} + +.moreText { + color: var(--color-azure); + font-size: 16px; +} diff --git a/src/components/Home/UseCases/Video/index.tsx b/src/components/Home/UseCases/Video/index.tsx new file mode 100644 index 0000000000..fff512e955 --- /dev/null +++ b/src/components/Home/UseCases/Video/index.tsx @@ -0,0 +1,52 @@ +import React, { useState, useCallback } from 'react' + +import TwoRowsButton from '../../../TwoRowsButton' +import { logEvent } from '../../../../utils/front/ga' + +import styles from './styles.module.css' + +const Video: React.SFC<{ id: string }> = ({ id }) => { + const [isWatching, setWatching] = useState(false) + + const watchVideo = useCallback(() => { + logEvent('button', 'video') + setWatching(true) + }, []) + + return ( +
+
+ {!isWatching && ( +
+ + } + onClick={watchVideo} + /> +
+ )} +