Projects STRLCPY gradejs Commits 6a377a9c
🤬
  • feat: a bunch of new svg icons, further WIP work on Search Results components (button, chips, header, package)

  • Loading...
  • Dmitry Shakun committed 2 years ago
    6a377a9c
    1 parent 097b3107
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■ ■ ■
    packages/web/package.json
    skipped 31 lines
    32 32   "@types/react": "^17.0.21",
    33 33   "@types/react-dom": "^17.0.9",
    34 34   "@types/react-gtm-module": "^2.0.1",
     35 + "@types/react-transition-group": "^4.4.5",
    35 36   "@types/semver": "^7.3.9",
    36 37   "babel-loader": "^8.2.2",
    37 38   "copy-webpack-plugin": "^11.0.0",
    skipped 25 lines
    63 64   "react-hook-form": "^7.15.3",
    64 65   "react-redux": "^8.0.2",
    65 66   "react-router-dom": "^6.3.0",
     67 + "react-transition-group": "^4.4.5",
    66 68   "semver": "^7.3.7"
    67 69   }
    68 70  }
    skipped 1 lines
  • packages/web/src/assets/checkbox-check.svg
  • packages/web/src/assets/icons/sprite/arrow-down.svg
  • packages/web/src/assets/icons/sprite/bug-outlined.svg
  • packages/web/src/assets/icons/sprite/cross.svg
  • packages/web/src/assets/icons/sprite/dependency.svg
  • packages/web/src/assets/icons/sprite/duplicate.svg
  • packages/web/src/assets/icons/sprite/graph.svg
  • packages/web/src/assets/icons/sprite/license.svg
  • packages/web/src/assets/icons/sprite/link.svg
  • packages/web/src/assets/icons/sprite/npm.svg
  • packages/web/src/assets/icons/sprite/outdated.svg
  • packages/web/src/assets/icons/sprite/question.svg
  • packages/web/src/assets/icons/sprite/rating-arrow.svg
  • packages/web/src/assets/icons/sprite/rating.svg
  • packages/web/src/assets/icons/sprite/repository.svg
  • packages/web/src/assets/icons/sprite/script.svg
  • packages/web/src/assets/icons/sprite/search.svg
  • packages/web/src/assets/icons/sprite/weight.svg
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/layouts/SearchResults/SearchResults.module.scss
     1 +@import '~styles/_vars.scss';
     2 +@import '~styles/responsive.scss';
     3 + 
     4 +.searchResults {
     5 + display: grid;
     6 + grid-template-columns: 260px minmax(0, 1fr);
     7 + grid-template-rows: min-content 1fr;
     8 + grid-column-gap: 80px;
     9 + margin-top: 44px;
     10 + margin-bottom: 100px;
     11 + 
     12 + @include mobile-and-tablet {
     13 + margin-top: 24px;
     14 + grid-template-columns: minmax(0, 1fr);
     15 + grid-template-rows: none;
     16 + grid-gap: 24px;
     17 + }
     18 +}
     19 + 
     20 +.packages {
     21 + grid-column: 2 / -1;
     22 + 
     23 + @include mobile-and-tablet {
     24 + grid-column: 1;
     25 + margin-left: -20px;
     26 + margin-right: -20px;
     27 + }
     28 +}
     29 + 
     30 +.searchedPackage {
     31 + grid-column: 2 / -1;
     32 + display: flex;
     33 + align-items: flex-start;
     34 + margin-bottom: 36px;
     35 + 
     36 + @include mobile-and-tablet {
     37 + flex-direction: column;
     38 + grid-column: 1;
     39 + margin-bottom: 0;
     40 + }
     41 +}
     42 + 
     43 +.searchedPackageImageWrapper {
     44 + display: flex;
     45 + flex-shrink: 0;
     46 + margin-right: 24px;
     47 + max-width: 52px;
     48 + 
     49 + @include mobile-and-tablet {
     50 + max-width: 44px;
     51 + margin-right: 0;
     52 + margin-bottom: 24px;
     53 + }
     54 +}
     55 + 
     56 +.searchedPackageImage {
     57 + width: 100%;
     58 + object-fit: cover;
     59 +}
     60 + 
     61 +.searchedPackageContent {
     62 + display: flex;
     63 + flex-direction: column;
     64 +}
     65 + 
     66 +.searchedPackageTitle {
     67 + margin-bottom: 12px;
     68 + 
     69 + @include mobile-and-tablet {
     70 + margin-bottom: 8px;
     71 + }
     72 +}
     73 + 
     74 +.searchedPackageHighlight {
     75 + color: $gray-text;
     76 +}
     77 + 
     78 +.searchedPackageSubtitle {
     79 + color: $gray-text;
     80 +}
     81 + 
     82 +.sidebar {
     83 + grid-column: 1 / 2;
     84 + grid-row: 1 / 3;
     85 + display: grid;
     86 + grid-gap: 24px;
     87 + align-self: start;
     88 + 
     89 + @include mobile-and-tablet {
     90 + grid-column: 1;
     91 + grid-row: 2;
     92 + }
     93 +}
     94 + 
     95 +.sidebarItem {
     96 + &:not(:first-child) {
     97 + padding-top: 24px;
     98 + border-top: 1px solid $gray-border;
     99 + }
     100 +}
     101 + 
     102 +.sidebarItemTop {
     103 + display: flex;
     104 + align-items: center;
     105 + justify-content: space-between;
     106 + margin-bottom: 16px;
     107 +}
     108 + 
     109 +.sidebarItemTitle {
     110 + font-weight: 500;
     111 + font-size: 19px;
     112 + line-height: 26px;
     113 +}
     114 + 
     115 +.sidebarItemAction {
     116 + display: flex;
     117 +}
     118 + 
     119 +.meta {
     120 + display: grid;
     121 + grid-gap: 20px;
     122 + 
     123 + // FIXME: not sure that mobile spacing should be higher than desktop one
     124 + @include mobile-and-tablet {
     125 + grid-gap: 24px;
     126 + }
     127 +}
     128 + 
     129 +.metaItem {
     130 + display: flex;
     131 + align-items: flex-start;
     132 + 
     133 + @include mobile-and-tablet {
     134 + line-height: 24px;
     135 + }
     136 +}
     137 + 
     138 +.metaIcon {
     139 + display: flex;
     140 + flex-shrink: 0;
     141 + margin-right: 20px;
     142 + 
     143 + @include mobile-and-tablet {
     144 + margin-right: 16px;
     145 + }
     146 +}
     147 + 
     148 +.metaText {
     149 + font-weight: 500;
     150 +}
     151 + 
     152 +.viewAll {
     153 + margin-top: 4px;
     154 + cursor: pointer;
     155 + font-weight: 500;
     156 + color: $blue-accent;
     157 +}
     158 + 
     159 +.checkboxGroup {
     160 + display: grid;
     161 + grid-gap: 18px;
     162 +}
     163 + 
     164 +.checkbox {
     165 + cursor: pointer;
     166 + display: inline-block;
     167 + position: relative;
     168 + padding-left: 36px;
     169 + 
     170 + @include lg {
     171 + line-height: 24px;
     172 + }
     173 +}
     174 + 
     175 +.checkboxInput {
     176 + position: absolute;
     177 + top: 0;
     178 + left: 0;
     179 + margin: 0;
     180 + padding: 0;
     181 + opacity: 0;
     182 + 
     183 + &:checked ~ .checkboxName {
     184 + &::before {
     185 + background: $black;
     186 + }
     187 + 
     188 + &::after {
     189 + opacity: 1;
     190 + }
     191 + }
     192 +}
     193 + 
     194 +.checkboxName {
     195 + display: inline-block;
     196 + 
     197 + &::before {
     198 + content: '';
     199 + position: absolute;
     200 + top: 0;
     201 + left: 0;
     202 + width: 24px;
     203 + height: 24px;
     204 + border: 2px solid transparent;
     205 + border-radius: 5px;
     206 + background: $gray-border;
     207 + transition: background 0.2s ease-out;
     208 + }
     209 + 
     210 + &::after {
     211 + content: '';
     212 + position: absolute;
     213 + top: 7px;
     214 + left: 6px;
     215 + width: 12px;
     216 + height: 10px;
     217 + opacity: 0;
     218 + transition: opacity 0.2s ease-out;
     219 + background: url('~assets/checkbox-check.svg') 50% no-repeat;
     220 + }
     221 +}
     222 + 
     223 +.authors {
     224 + display: flex;
     225 + flex-wrap: wrap;
     226 +}
     227 + 
     228 +.author {
     229 + display: flex;
     230 + align-items: center;
     231 + margin-bottom: 10px;
     232 + 
     233 + &:not(:last-child) {
     234 + margin-right: 16px;
     235 + }
     236 +}
     237 + 
     238 +.authorImageWrapper {
     239 + display: flex;
     240 + flex-shrink: 0;
     241 + margin-right: 6px;
     242 + max-width: 36px;
     243 +}
     244 + 
     245 +.authorImage {
     246 + width: 100%;
     247 + object-fit: cover;
     248 + border-radius: 50%;
     249 +}
     250 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/layouts/SearchResults/SearchResults.stories.tsx
     1 +import React from 'react';
     2 +import { ComponentMeta, ComponentStory } from '@storybook/react';
     3 +import SearchResults from './SearchResults';
     4 + 
     5 +export default {
     6 + title: 'Layouts / Search Results',
     7 + component: SearchResults,
     8 + parameters: {
     9 + layout: 'fullscreen',
     10 + },
     11 +} as ComponentMeta<typeof SearchResults>;
     12 + 
     13 +const Template: ComponentStory<typeof SearchResults> = (args) => <SearchResults {...args} />;
     14 + 
     15 +export const Default = Template.bind({});
     16 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/layouts/SearchResults/SearchResults.tsx
     1 +import React from 'react';
     2 +import styles from './SearchResults.module.scss';
     3 +import Header from 'components/ui/Header/Header';
     4 +import Footer from 'components/ui/Footer/Footer';
     5 +import Container from 'components/ui/Container/Container';
     6 +import { Icon } from '../../ui/Icon/Icon';
     7 +import ChipGroup from '../../ui/ChipGroup/ChipGroup';
     8 +import PackagePreview from '../../ui/PackagePreview/PackagePreview';
     9 +import SearchBar from '../../ui/SearchBar/SearchBar';
     10 + 
     11 +type Props = {};
     12 + 
     13 +export default function SearchResults(props: Props) {
     14 + return (
     15 + <>
     16 + <Header>
     17 + <SearchBar value='pinterest.com/blog/%D0%92%D092%D092%D092%/dFD092fg092%D092%/dFD092/blog/%D0%92%D092%D092%D092%/dFD092fg092%D092%/dFD092f' />
     18 + </Header>
     19 + 
     20 + <Container>
     21 + <div className={styles.searchResults}>
     22 + <div className={styles.searchedPackage}>
     23 + <div className={styles.searchedPackageImageWrapper}>
     24 + <img
     25 + className={styles.searchedPackageImage}
     26 + src='https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg'
     27 + alt=''
     28 + />
     29 + </div>
     30 + <div className={styles.searchedPackageContent}>
     31 + <h3 className={styles.searchedPackageTitle}>
     32 + pinterest.com <span className={styles.searchedPackageHighlight}>6 packages</span>
     33 + </h3>
     34 + <div className={styles.searchedPackageSubtitle}>Last scanning 21 feb in 21:30</div>
     35 + </div>
     36 + </div>
     37 + 
     38 + <aside className={styles.sidebar}>
     39 + <div className={styles.sidebarItem}>
     40 + <div className={styles.meta}>
     41 + <div className={styles.metaItem}>
     42 + <span className={styles.metaIcon}>
     43 + <Icon kind='weight' width={24} height={24} />
     44 + </span>
     45 + <span className={styles.metaText}>159 kb webpack bundle size</span>
     46 + </div>
     47 + <div className={styles.metaItem}>
     48 + <span className={styles.metaIcon}>
     49 + <Icon kind='search' width={24} height={24} />
     50 + </span>
     51 + <span className={styles.metaText}>50 scripts found</span>
     52 + </div>
     53 + <div className={styles.metaItem}>
     54 + <span className={styles.metaIcon}>
     55 + <Icon kind='bug' width={24} height={24} color='#F3512E' />
     56 + </span>
     57 + <span className={styles.metaText}>6 vulnerabilities in 4&nbsp;packages</span>
     58 + </div>
     59 + <div className={styles.metaItem}>
     60 + <span className={styles.metaIcon}>
     61 + <Icon kind='duplicate' color='#F3812E' width={24} height={24} />
     62 + </span>
     63 + <span className={styles.metaText}>12 duplicate packages</span>
     64 + </div>
     65 + <div className={styles.metaItem}>
     66 + <span className={styles.metaIcon}>
     67 + <Icon kind='outdated' color='#F1CE61' stroke='white' width={24} height={24} />
     68 + </span>
     69 + <span className={styles.metaText}>18 outdated packages</span>
     70 + </div>
     71 + </div>
     72 + </div>
     73 + 
     74 + <div className={styles.sidebarItem}>
     75 + <div className={styles.sidebarItemTop}>
     76 + <div className={styles.sidebarItemTitle}>Keywords</div>
     77 + <div className={styles.sidebarItemAction}>
     78 + <Icon kind='search' width={24} height={24} />
     79 + </div>
     80 + </div>
     81 + 
     82 + <ChipGroup chips={['#moment', '#date', '#react', '#parse', '#fb']} />
     83 + <span role='button' className={styles.viewAll}>
     84 + View All
     85 + </span>
     86 + </div>
     87 + 
     88 + <div className={styles.sidebarItem}>
     89 + <div className={styles.sidebarItemTop}>
     90 + <div className={styles.sidebarItemTitle}>Problem</div>
     91 + </div>
     92 + 
     93 + <div className={styles.checkboxGroup}>
     94 + <label className={styles.checkbox}>
     95 + <input type='checkbox' className={styles.checkboxInput} />
     96 + <span className={styles.checkboxName}>Vulnerabilities</span>
     97 + </label>
     98 + <label className={styles.checkbox}>
     99 + <input type='checkbox' className={styles.checkboxInput} />
     100 + <span className={styles.checkboxName}>Outdated</span>
     101 + </label>
     102 + <label className={styles.checkbox}>
     103 + <input type='checkbox' className={styles.checkboxInput} />
     104 + <span className={styles.checkboxName}>Duplicate</span>
     105 + </label>
     106 + </div>
     107 + </div>
     108 + 
     109 + <div className={styles.sidebarItem}>
     110 + <div className={styles.sidebarItemTop}>
     111 + <div className={styles.sidebarItemTitle}>Authors</div>
     112 + <div className={styles.sidebarItemAction}>
     113 + <Icon kind='search' width={24} height={24} />
     114 + </div>
     115 + </div>
     116 + 
     117 + <div className={styles.authors}>
     118 + <div className={styles.author}>
     119 + <div className={styles.authorImageWrapper}>
     120 + <img
     121 + className={styles.authorImage}
     122 + src='https://via.placeholder.com/36'
     123 + alt=''
     124 + />
     125 + </div>
     126 + <div className={styles.authorName}>acdlite</div>
     127 + </div>
     128 + 
     129 + <div className={styles.author}>
     130 + <div className={styles.authorImageWrapper}>
     131 + <img
     132 + className={styles.authorImage}
     133 + src='https://via.placeholder.com/36'
     134 + alt=''
     135 + />
     136 + </div>
     137 + <div className={styles.authorName}>gaearon</div>
     138 + </div>
     139 + 
     140 + <div className={styles.author}>
     141 + <div className={styles.authorImageWrapper}>
     142 + <img
     143 + className={styles.authorImage}
     144 + src='https://via.placeholder.com/36'
     145 + alt=''
     146 + />
     147 + </div>
     148 + <div className={styles.authorName}>sophiebits</div>
     149 + </div>
     150 + 
     151 + <div className={styles.author}>
     152 + <div className={styles.authorImageWrapper}>
     153 + <img
     154 + className={styles.authorImage}
     155 + src='https://via.placeholder.com/36'
     156 + alt=''
     157 + />
     158 + </div>
     159 + <div className={styles.authorName}>trueadm</div>
     160 + </div>
     161 + </div>
     162 + 
     163 + <span role='button' className={styles.viewAll}>
     164 + View All
     165 + </span>
     166 + </div>
     167 + </aside>
     168 + 
     169 + <div className={styles.packages}>
     170 + <PackagePreview
     171 + name='@team-griffin/react-heading-section@team-griffin/react-heading-section'
     172 + version='3.0.0 - 4.16.4'
     173 + />
     174 + </div>
     175 + </div>
     176 + </Container>
     177 + 
     178 + <Footer />
     179 + </>
     180 + );
     181 +}
     182 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Button/Button.module.scss
     1 +@import '~styles/_vars.scss';
    1 2  @import '~styles/responsive.scss';
    2 3   
     4 +//.button {
     5 +// appearance: none;
     6 +// border-radius: 6px;
     7 +// border: none;
     8 +// outline: none;
     9 +// cursor: pointer;
     10 +// line-height: 140%;
     11 +// font-family: 'Inter', sans-serif;
     12 +// font-weight: 400;
     13 +// box-sizing: border-box;
     14 +//}
     15 +//
     16 +//// Sizes
     17 +//.big {
     18 +// padding: 16px 16px;
     19 +// font-size: 16px;
     20 +// line-height: 28px;
     21 +//
     22 +// @include mobile {
     23 +// font-size: 16px;
     24 +// line-height: 22px;
     25 +// }
     26 +//}
     27 +//
     28 +//.medium {
     29 +// padding: 12px 10px 12px;
     30 +// font-size: 14px;
     31 +// line-height: 18px;
     32 +//}
     33 +//
     34 +//// Variants
     35 +//.default {
     36 +// background: #fff;
     37 +// border: 1px solid #e6e6e6;
     38 +// color: #0f0f0f;
     39 +// transition: border-color 0.2s;
     40 +//
     41 +// &:enabled:hover {
     42 +// border-color: #a5a5a5;
     43 +// }
     44 +//
     45 +// &:enabled:active {
     46 +// border-color: #000000;
     47 +// }
     48 +//}
     49 +//
     50 +//.black {
     51 +// background-color: #0f0f0f;
     52 +// color: #fff;
     53 +//}
     54 +//
     55 +//.action {
     56 +// background: url('~assets/icons/arrow-right.svg') right 20px center / 20px 21px no-repeat;
     57 +// background-color: #0f0f0f;
     58 +// color: #fff;
     59 +// text-align: left;
     60 +// padding-right: 60px;
     61 +//}
     62 +//
     63 +//.black,
     64 +//.action {
     65 +// transition: background-color 0.2s;
     66 +//
     67 +// &:enabled:hover {
     68 +// background-color: #666666;
     69 +// }
     70 +// &:enabled:active {
     71 +// background-color: #a5a5a5;
     72 +// }
     73 +//}
     74 +//
     75 +//.action:disabled {
     76 +// background-color: #666666;
     77 +// background-image: url('~assets/loader-white.svg');
     78 +//}
     79 + 
    3 80  .button {
     81 + position: relative;
     82 + display: inline-flex;
     83 + align-items: center;
     84 + justify-content: center;
     85 + outline: 0;
     86 + cursor: pointer;
     87 + user-select: none;
     88 + vertical-align: middle;
    4 89   appearance: none;
    5  - border-radius: 6px;
     90 + text-decoration: none;
     91 + font-family: inherit;
     92 + font-size: 16px;
     93 + line-height: 26px;
     94 + font-weight: 500;
    6 95   border: none;
    7  - outline: none;
    8  - cursor: pointer;
    9  - line-height: 140%;
    10  - font-family: 'Inter', sans-serif;
    11  - font-weight: 400;
    12  - box-sizing: border-box;
     96 + padding: 13px 20px;
     97 + background-color: $blue-accent;
     98 + color: $white;
     99 + border-radius: 67px;
    13 100  }
    14 101   
    15  -// Sizes
    16  -.big {
    17  - padding: 16px 16px;
    18  - font-size: 16px;
    19  - line-height: 28px;
    20  - 
    21  - @include mobile {
    22  - font-size: 16px;
    23  - line-height: 22px;
    24  - }
     102 +.arrowIcon {
     103 + position: absolute;
     104 + top: 3px;
     105 + right: 3px;
     106 + width: 46px;
     107 + height: 46px;
     108 + display: flex;
     109 + align-items: center;
     110 + justify-content: center;
     111 + margin-left: 20px;
    25 112  }
    26 113   
    27  -.medium {
    28  - padding: 12px 10px 12px;
    29  - font-size: 14px;
    30  - line-height: 18px;
     114 +.arrowIconBackground {
     115 + position: absolute;
     116 + top: 0;
     117 + left: 0;
     118 + width: 100%;
     119 + height: 100%;
     120 + border-radius: 50%;
     121 + background-color: rgba($white, 0.2);
     122 + transition: all $transition-duration $transition-timing-function;
    31 123  }
    32 124   
    33  -// Variants
    34  -.default {
    35  - background: #fff;
    36  - border: 1px solid #e6e6e6;
    37  - color: #0f0f0f;
    38  - transition: border-color 0.2s;
     125 +.arrow {
     126 + padding-right: 69px;
    39 127   
    40  - &:enabled:hover {
    41  - border-color: #a5a5a5;
     128 + .chevron {
     129 + transform: translateX(-3px);
     130 + transition: transform $transition-duration $transition-timing-function;
    42 131   }
    43 132   
    44  - &:enabled:active {
    45  - border-color: #000000;
     133 + .line {
     134 + transform: translateX(-3px);
     135 + opacity: 0;
     136 + stroke-dasharray: 1;
     137 + stroke-dashoffset: 0;
     138 + transition: all $transition-duration $transition-timing-function;
    46 139   }
    47  -}
    48 140   
    49  -.black {
    50  - background-color: #0f0f0f;
    51  - color: #fff;
    52  -}
     141 + &:hover {
     142 + .arrowIconBackground {
     143 + transform: scale(0.9);
     144 + }
    53 145   
    54  -.action {
    55  - background: url('~assets/icons/arrow-right.svg') right 20px center / 20px 21px no-repeat;
    56  - background-color: #0f0f0f;
    57  - color: #fff;
    58  - text-align: left;
    59  - padding-right: 60px;
    60  -}
    61  - 
    62  -.black,
    63  -.action {
    64  - transition: background-color 0.2s;
     146 + .chevron {
     147 + transform: translateX(0);
     148 + }
    65 149   
    66  - &:enabled:hover {
    67  - background-color: #666666;
    68  - }
    69  - &:enabled:active {
    70  - background-color: #a5a5a5;
     150 + .line {
     151 + transform: translateX(0);
     152 + opacity: 1;
     153 + animation: reveal $transition-duration $transition-timing-function forwards;
     154 + }
    71 155   }
    72 156  }
    73 157   
    74  -.action:disabled {
    75  - background-color: #666666;
    76  - background-image: url('~assets/loader-white.svg');
     158 +@keyframes reveal {
     159 + from {
     160 + stroke: none;
     161 + stroke-dashoffset: 2;
     162 + }
     163 + to {
     164 + stroke: $white;
     165 + stroke-dashoffset: 0;
     166 + }
    77 167  }
    78 168   
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Button/Button.stories.tsx
    skipped 13 lines
    14 14   children: 'Button',
    15 15  };
    16 16   
     17 +export const Arrow = (args: Props) => <Button {...args} />;
     18 +Arrow.args = {
     19 + children: 'Button',
     20 + variant: 'arrow',
     21 +};
     22 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Button/Button.tsx
    skipped 6 lines
    7 7   className?: string;
    8 8   children: React.ReactNode;
    9 9   type?: React.ButtonHTMLAttributes<HTMLButtonElement>['type'];
    10  - variant?: 'default' | 'black' | 'action';
     10 + variant?: 'default' | 'arrow';
    11 11   size?: 'medium' | 'big';
    12 12   disabled?: boolean;
    13 13   onClick?: MouseEventHandler;
    skipped 16 lines
    30 30   disabled={disabled}
    31 31   >
    32 32   {children}
     33 + {variant === 'arrow' && (
     34 + <span className={styles.arrowIcon}>
     35 + <span className={styles.arrowIconBackground} />
     36 + <svg
     37 + width='24'
     38 + height='24'
     39 + viewBox='0 0 24 24'
     40 + fill='none'
     41 + xmlns='http://www.w3.org/2000/svg'
     42 + >
     43 + <path
     44 + className={styles.chevron}
     45 + d='M13 18L19 12L13 6'
     46 + stroke='white'
     47 + strokeWidth='2'
     48 + strokeLinecap='round'
     49 + strokeLinejoin='round'
     50 + />
     51 + <path
     52 + className={styles.line}
     53 + pathLength='1'
     54 + d='M19 12L5 12'
     55 + stroke='white'
     56 + strokeWidth='2'
     57 + strokeLinecap='round'
     58 + strokeLinejoin='round'
     59 + />
     60 + </svg>
     61 + </span>
     62 + )}
    33 63   </button>
    34 64   );
    35 65  }
    skipped 1 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Chip/Chip.module.scss
    skipped 12 lines
    13 13   
    14 14   @include mobile-and-tablet {
    15 15   font-size: 14px;
     16 + line-height: 22px;
    16 17   }
     18 +}
     19 + 
     20 +.icon {
     21 + display: inline-flex;
     22 + margin-right: 2px;
    17 23  }
    18 24   
    19 25  .secondary {
    skipped 10 lines
    30 36   color: rgba($white, 0.8);
    31 37  }
    32 38   
    33  -.regular {
     39 +.badge {
     40 + padding: 0 9px 0 3px;
     41 + 
     42 + @include mobile-and-tablet {
     43 + font-size: 15px;
     44 + line-height: 17px;
     45 + padding: 2px 10px 2px 2px;
     46 + 
     47 + .icon svg {
     48 + width: 18px;
     49 + height: 18px;
     50 + }
     51 + }
     52 +}
     53 + 
     54 +.default {
    34 55   padding: 4px 11px;
    35 56  }
    36 57   
    37 58  .medium {
    38 59   padding: 7px 11px;
     60 + 
     61 + @include mobile-and-tablet {
     62 + padding: 4px 11px;
     63 + }
    39 64  }
    40 65   
    41 66  .large {
    skipped 5 lines
    47 72  }
    48 73   
    49 74  .sans-serif {
    50  - font-size: $font-sans-serif;
     75 + font-family: $font-sans-serif;
    51 76  }
    52 77   
    53 78  .monospace {
    54 79   font-family: $font-monospace;
    55 80  }
    56 81   
     82 +.400 {
     83 + font-weight: normal;
     84 +}
     85 + 
     86 +.500 {
     87 + font-weight: 500;
     88 +}
     89 + 
     90 +.red {
     91 + background-color: $red-accent;
     92 + color: $white;
     93 +}
     94 + 
     95 +.orange {
     96 + background-color: $orange-accent;
     97 + color: $white;
     98 +}
     99 + 
     100 +.yellow {
     101 + background-color: $yellow-accent;
     102 + color: $white;
     103 +}
     104 + 
     105 +.blueAdditional {
     106 + background-color: $blue-additional;
     107 + color: $blue-accent;
     108 +}
     109 + 
     110 +.smallFont {
     111 + font-size: 14px;
     112 + line-height: 20px;
     113 +}
     114 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Chip/Chip.tsx
    skipped 9 lines
    10 10   size?: 'default' | 'medium' | 'large' | string;
    11 11   font?: 'sans-serif' | 'monospace' | string;
    12 12   fontWeight?: 400 | 500;
     13 + fontSize?: 'small' | 'regular';
    13 14   icon?: React.ReactElement<IconProps>;
    14 15  };
    15 16   
    skipped 4 lines
    20 21   size = 'default',
    21 22   font = 'sans-serif',
    22 23   fontWeight = 400,
     24 + fontSize = 'regular',
    23 25   icon,
    24 26  }: ChipProps) {
    25 27   return (
    skipped 4 lines
    30 32   styles[size],
    31 33   styles[font],
    32 34   styles[fontWeight],
     35 + fontSize === 'small' && styles.smallFont,
    33 36   className
    34 37   )}
    35 38   >
    skipped 6 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/ChipGroup/ChipGroup.module.scss
    skipped 13 lines
    14 14  }
    15 15   
    16 16  .chip {
    17  - margin-bottom: 0.75rem;
     17 + margin-bottom: 8px;
    18 18   
    19 19   &:not(:last-child) {
    20  - margin-right: 0.5rem;
     20 + margin-right: 8px;
    21 21   }
    22 22  }
    23 23   
  • ■ ■ ■ ■ ■
    packages/web/src/components/ui/ChipGroup/ChipGroup.tsx
    skipped 6 lines
    7 7   children?: React.ReactNode;
    8 8   size?: ChipProps['size'];
    9 9   font?: ChipProps['font'];
     10 + fontSize?: ChipProps['fontSize'];
    10 11  };
    11 12   
    12  -// TODO: allow Chip customizing with props
    13  -export default function ChipGroup({ chips, children, size = 'medium', font = 'monospace' }: Props) {
     13 +export default function ChipGroup({
     14 + chips,
     15 + children,
     16 + size = 'medium',
     17 + font = 'monospace',
     18 + fontSize = 'regular',
     19 +}: Props) {
    14 20   return (
    15 21   <div className={styles.chipsWrapper}>
    16 22   <div className={styles.chips}>
    17 23   {chips.map((chip) => (
    18  - <Chip key={chip} className={styles.chip} size={size} font={font}>
     24 + <Chip key={chip} className={styles.chip} size={size} font={font} fontSize={fontSize}>
    19 25   {chip}
    20 26   </Chip>
    21 27   ))}
    skipped 7 lines
  • ■ ■ ■ ■ ■
    packages/web/src/components/ui/Header/Header.module.scss
    skipped 4 lines
    5 5   display: flex;
    6 6   align-items: center;
    7 7   justify-content: space-between;
    8  - padding: 32px 0;
     8 + padding: 20px 0;
    9 9   
    10 10   @include mobile-and-tablet {
    11  - padding: 22px 0;
     11 + display: grid;
     12 + grid-row-gap: 22px;
     13 + grid-template-columns: 1fr 2fr;
     14 + grid-template-rows: 28px 52px;
    12 15   }
    13 16  }
    14 17   
    skipped 6 lines
    21 24   }
    22 25  }
    23 26   
     27 +.searchWrapper {
     28 + flex-grow: 1;
     29 + padding: 0 40px;
     30 + 
     31 + @include mobile-and-tablet {
     32 + grid-column: 1 / -1;
     33 + grid-row: 2 / -1;
     34 + padding: 0;
     35 + }
     36 +}
     37 + 
    24 38  .nav {
    25 39   display: flex;
    26 40   align-items: center;
     41 + justify-content: flex-end;
    27 42   
    28 43   & > * + * {
    29 44   margin-left: 40px;
    skipped 34 lines
  • ■ ■ ■ ■
    packages/web/src/components/ui/Header/Header.tsx
    skipped 22 lines
    23 23   />
    24 24   </a>
    25 25   
    26  - {children}
     26 + <div className={styles.searchWrapper}>{children}</div>
    27 27   
    28 28   <div className={styles.nav}>
    29 29   <a
    skipped 38 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Icon/Icon.tsx
    skipped 10 lines
    11 11  import search from '../../../assets/icons/sprite/search.svg';
    12 12  import duplicate from '../../../assets/icons/sprite/duplicate.svg';
    13 13  import outdated from '../../../assets/icons/sprite/outdated.svg';
     14 +import cross from '../../../assets/icons/sprite/cross.svg';
     15 +import script from '../../../assets/icons/sprite/script.svg';
     16 +import license from '../../../assets/icons/sprite/license.svg';
     17 +import bugOutlined from '../../../assets/icons/sprite/bug-outlined.svg';
     18 +import repository from '../../../assets/icons/sprite/repository.svg';
     19 +import link from '../../../assets/icons/sprite/link.svg';
     20 +import npm from '../../../assets/icons/sprite/npm.svg';
     21 +import rating from '../../../assets/icons/sprite/rating.svg';
     22 +import dependency from '../../../assets/icons/sprite/dependency.svg';
     23 +import graph from '../../../assets/icons/sprite/graph.svg';
     24 +import ratingArrow from '../../../assets/icons/sprite/rating-arrow.svg';
    14 25   
    15 26  const icons = {
    16 27   githubLogo,
    skipped 8 lines
    25 36   duplicate,
    26 37   outdated,
    27 38   arrowDown,
     39 + cross,
     40 + script,
     41 + license,
     42 + bugOutlined,
     43 + repository,
     44 + link,
     45 + npm,
     46 + rating,
     47 + dependency,
     48 + graph,
     49 + ratingArrow,
    28 50  };
    29 51   
    30 52  export type IconProps = {
    skipped 35 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/PackagePreview/PackagePreview.module.scss
     1 +@import '~styles/_vars.scss';
     2 +@import '~styles/responsive.scss';
     3 + 
     4 +.package {
     5 + padding: 32px 36px;
     6 + border-radius: 24px;
     7 + background: $white;
     8 + box-shadow: $shadow;
     9 + 
     10 + @include mobile-and-tablet {
     11 + padding: 20px;
     12 + }
     13 +}
     14 + 
     15 +.header {
     16 + padding-bottom: 24px;
     17 +}
     18 + 
     19 +.top {
     20 + cursor: pointer;
     21 + display: flex;
     22 + align-items: flex-start;
     23 + justify-content: space-between;
     24 +}
     25 + 
     26 +.title {
     27 + padding-right: 24px;
     28 + 
     29 + @include mobile-and-tablet {
     30 + display: flex;
     31 + flex-direction: column-reverse;
     32 + }
     33 +}
     34 + 
     35 +.name {
     36 + font-size: 26px;
     37 + font-weight: 500;
     38 +}
     39 + 
     40 +.version {
     41 + font-size: 26px;
     42 + font-weight: 400;
     43 + color: $gray-text;
     44 + margin-right: 12px;
     45 +}
     46 + 
     47 +.problems {
     48 + vertical-align: text-bottom;
     49 + display: inline-flex;
     50 + align-items: center;
     51 + font-size: 16px;
     52 + height: 33px;
     53 + 
     54 + & > * {
     55 + &:not(:last-child) {
     56 + margin-right: 6px;
     57 + }
     58 + 
     59 + @include mobile-and-tablet {
     60 + margin-bottom: 6px;
     61 + }
     62 + }
     63 + 
     64 + @include mobile-and-tablet {
     65 + display: flex;
     66 + flex-wrap: wrap;
     67 + order: 1;
     68 + height: initial;
     69 + margin-bottom: 2px;
     70 + }
     71 +}
     72 + 
     73 +.arrowWrapper {
     74 + cursor: pointer;
     75 + appearance: none;
     76 + border: none;
     77 + flex-shrink: 0;
     78 + width: 36px;
     79 + height: 36px;
     80 + border-radius: 50%;
     81 + background: $gray-surface;
     82 + display: flex;
     83 + align-items: center;
     84 + justify-content: center;
     85 + 
     86 + // FIXME: not sure that 24x24 is good UX size for clickable element,
     87 + // i'm considering at least 32x32, or not changing desktop 36x36 at all
     88 + //@include mobile-and-tablet {
     89 + // width: 24px;
     90 + // height: 24px;
     91 + //}
     92 +}
     93 + 
     94 +.arrow {
     95 + transition: transform 0.3s ease-out;
     96 + 
     97 + .open & {
     98 + transform: rotate(180deg);
     99 + }
     100 +}
     101 + 
     102 +.desc {
     103 + margin-top: 8px;
     104 + color: $gray-text;
     105 +}
     106 + 
     107 +.footer {
     108 + display: flex;
     109 + align-items: center;
     110 + justify-content: space-between;
     111 + border-top: 1px solid $gray-border;
     112 + padding-top: 24px;
     113 + 
     114 + @include mobile-and-tablet {
     115 + padding-top: 20px;
     116 + }
     117 +}
     118 + 
     119 +.tags {
     120 + display: flex;
     121 + align-items: center;
     122 + 
     123 + & > * + * {
     124 + margin-left: 24px;
     125 + }
     126 +}
     127 + 
     128 +.tag {
     129 + font-weight: 500;
     130 + 
     131 + @include mobile-and-tablet {
     132 + &:nth-child(n + 3) {
     133 + display: none;
     134 + }
     135 + }
     136 +}
     137 + 
     138 +.author {
     139 + display: flex;
     140 + align-items: center;
     141 + color: $gray-text;
     142 +}
     143 + 
     144 +.authorImage {
     145 + flex-shrink: 0;
     146 + width: 36px;
     147 + height: 36px;
     148 + border-radius: 50%;
     149 + margin-left: 16px;
     150 + 
     151 + @include mobile-and-tablet {
     152 + width: 32px;
     153 + height: 32px;
     154 + }
     155 +}
     156 + 
     157 +.authorName {
     158 + @include mobile-and-tablet {
     159 + display: none;
     160 + }
     161 +}
     162 + 
     163 +.content {
     164 + max-height: 0;
     165 + overflow: hidden;
     166 + transition-duration: $transition-duration;
     167 + transition-timing-function: $transition-timing-function;
     168 + 
     169 + .open & {
     170 + max-height: 9000px;
     171 + transition-duration: $transition-duration;
     172 + transition-timing-function: ease-in;
     173 + }
     174 +}
     175 + 
     176 +.contentEnterDone {
     177 + overflow: visible;
     178 +}
     179 + 
     180 +.contentInner {
     181 + margin-bottom: 24px;
     182 +}
     183 + 
     184 +.stat {
     185 + margin-bottom: 32px;
     186 +}
     187 + 
     188 +.statHeader {
     189 + display: flex;
     190 + align-items: center;
     191 + font-size: 14px;
     192 + line-height: 18px;
     193 + color: $gray-text;
     194 + font-weight: 500;
     195 + margin-bottom: 8px;
     196 + 
     197 + @include mobile-and-tablet {
     198 + margin-bottom: 12px;
     199 + }
     200 +}
     201 + 
     202 +.statIcon {
     203 + margin-right: 8px;
     204 +}
     205 + 
     206 +.statLink {
     207 + display: block;
     208 + line-height: 20px;
     209 + color: $blue-accent;
     210 + font-weight: 500;
     211 +}
     212 + 
     213 +.statTitle {
     214 + display: flex;
     215 + align-items: flex-end;
     216 + font-size: 26px;
     217 + line-height: 40px;
     218 + font-weight: 500;
     219 +}
     220 + 
     221 +.statRating {
     222 + position: relative;
     223 + align-items: flex-end;
     224 + margin-left: 4px;
     225 + font-size: 16px;
     226 + line-height: 1.625;
     227 +}
     228 + 
     229 +.statRatingGreen {
     230 + color: $green-accent;
     231 + 
     232 + .statRatingArrow {
     233 + color: $green-accent;
     234 + }
     235 +}
     236 + 
     237 +.statRatingRed {
     238 + color: $red-accent;
     239 + 
     240 + .statRatingArrow {
     241 + color: $red-accent;
     242 + transform: rotate(90deg);
     243 + }
     244 +}
     245 + 
     246 +.statRatingArrow {
     247 + top: -8px;
     248 + right: 0;
     249 + position: absolute;
     250 +}
     251 + 
     252 +.statSubtitle {
     253 + color: $gray-text;
     254 +}
     255 + 
     256 +.statList {
     257 + display: flex;
     258 + align-items: flex-start;
     259 + justify-content: space-between;
     260 + padding-right: 60px;
     261 + 
     262 + @include mobile-and-tablet {
     263 + flex-wrap: wrap;
     264 + padding-right: 0;
     265 + }
     266 +}
     267 + 
     268 +.statListItemSmall {
     269 + flex: 0 0 auto;
     270 + width: 25%;
     271 + 
     272 + @include mobile-and-tablet {
     273 + width: 50%;
     274 + }
     275 +}
     276 + 
     277 +.statListItemLarge {
     278 + flex: 0 0 auto;
     279 + width: 50%;
     280 + 
     281 + @include mobile-and-tablet {
     282 + width: 100%;
     283 + }
     284 +}
     285 + 
     286 +.popularity {
     287 + margin-top: 16px;
     288 + display: grid;
     289 + grid-template-columns: repeat(6, 1fr);
     290 + grid-gap: 11px;
     291 + 
     292 + overflow-x: auto;
     293 + 
     294 + &::-webkit-scrollbar {
     295 + display: none;
     296 + }
     297 + 
     298 + @include mobile {
     299 + margin: 0 -20px;
     300 + padding: 0 20px;
     301 + display: flex;
     302 + flex-wrap: nowrap;
     303 + }
     304 +}
     305 + 
     306 +.popularityItemWrapper {
     307 +}
     308 + 
     309 +.popularityItem {
     310 + height: 282px;
     311 + background: $gray-surface;
     312 + border-radius: 12px;
     313 + display: flex;
     314 + flex-direction: column;
     315 + justify-content: flex-end;
     316 + 
     317 + @include mobile-and-tablet {
     318 + min-width: 92px;
     319 + }
     320 +}
     321 + 
     322 +.popularityFill {
     323 + border-radius: 10px;
     324 + background: $gray-border;
     325 + display: flex;
     326 + flex-direction: column;
     327 + justify-content: flex-end;
     328 + align-items: center;
     329 + font-weight: 500;
     330 + color: $gray-text;
     331 + padding-bottom: 14px;
     332 +}
     333 + 
     334 +.popularityFillAccent {
     335 + background: $blue-accent;
     336 + color: #ffffff;
     337 +}
     338 + 
     339 +.popularityVersion {
     340 + display: flex;
     341 + align-items: center;
     342 + justify-content: center;
     343 + margin-top: 16px;
     344 + font-family: $font-monospace;
     345 + font-weight: 500;
     346 +}
     347 + 
     348 +.popularityVersionIcon {
     349 + margin-left: 8px;
     350 +}
     351 + 
     352 +.actions {
     353 + display: flex;
     354 + align-items: center;
     355 + justify-content: space-between;
     356 + 
     357 + @include mobile-and-tablet {
     358 + flex-direction: column-reverse;
     359 + justify-content: flex-start;
     360 + align-items: flex-start;
     361 + }
     362 +}
     363 + 
     364 +.links {
     365 + display: flex;
     366 + align-items: center;
     367 + 
     368 + @include mobile-and-tablet {
     369 + justify-content: space-between;
     370 + width: 100%;
     371 + }
     372 + 
     373 + & > * + * {
     374 + margin-left: 62px;
     375 + 
     376 + @include mobile-and-tablet {
     377 + margin-left: 0;
     378 + }
     379 + }
     380 +}
     381 + 
     382 +.link {
     383 + display: inline-flex;
     384 + align-items: center;
     385 + color: $black;
     386 + font-weight: 500;
     387 +}
     388 + 
     389 +.linkIcon {
     390 + margin-right: 8px;
     391 +}
     392 + 
     393 +.details {
     394 + display: block;
     395 +}
     396 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/PackagePreview/PackagePreview.tsx
     1 +import React, { useState } from 'react';
     2 +import styles from './PackagePreview.module.scss';
     3 +import { Icon } from '../Icon/Icon';
     4 +import Chip from '../Chip/Chip';
     5 +import clsx from 'clsx';
     6 +import ChipGroup from '../ChipGroup/ChipGroup';
     7 +import SitesList, { Site } from '../SitesList/SitesList';
     8 +import { CSSTransition } from 'react-transition-group';
     9 +import Button from '../Button/Button';
     10 + 
     11 +type Props = {
     12 + name: string;
     13 + version: string;
     14 +};
     15 + 
     16 +export default function PackagePreview({ name, version }: Props) {
     17 + const [open, setOpen] = useState<boolean>(true);
     18 + 
     19 + // TODO: mock data, remove later
     20 + const sites: Site[] = [
     21 + {
     22 + id: '123',
     23 + image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     24 + name: 'pinterest.com',
     25 + packagesCount: 151,
     26 + },
     27 + {
     28 + id: '456',
     29 + image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     30 + name: 'pinterest.com',
     31 + packagesCount: 151,
     32 + },
     33 + {
     34 + id: '789',
     35 + image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     36 + name: 'pinterest.com',
     37 + packagesCount: 151,
     38 + },
     39 + {
     40 + id: '1231',
     41 + image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     42 + name: 'pinterest.com',
     43 + packagesCount: 151,
     44 + },
     45 + ];
     46 + 
     47 + const toggleOpen = () => {
     48 + setOpen(!open);
     49 + };
     50 + 
     51 + return (
     52 + <div className={clsx(styles.package, open && styles.open)}>
     53 + <header className={styles.header}>
     54 + <div className={styles.top} onClick={toggleOpen}>
     55 + <div className={styles.title}>
     56 + <span className={styles.name}>
     57 + {name} <span className={styles.version}>{version}</span>
     58 + </span>
     59 + <span className={styles.problems}>
     60 + <Chip
     61 + variant='red'
     62 + size='badge'
     63 + icon={<Icon kind='bug' width={24} height={24} color='white' />}
     64 + >
     65 + Vulnerabilities
     66 + </Chip>
     67 + <Chip
     68 + variant='orange'
     69 + size='badge'
     70 + icon={<Icon kind='duplicate' width={24} height={24} color='white' />}
     71 + >
     72 + Duplicate
     73 + </Chip>
     74 + <Chip
     75 + variant='yellow'
     76 + size='badge'
     77 + icon={
     78 + <Icon kind='outdated' width={24} height={24} color='white' stroke='#F1CE61' />
     79 + }
     80 + >
     81 + Outdated
     82 + </Chip>
     83 + </span>
     84 + </div>
     85 + 
     86 + <button type='button' className={styles.arrowWrapper} onClick={toggleOpen}>
     87 + {/* FIXME: requires different smaller svg icon for mobile and makes button 24x24 */}
     88 + {/* which is probably not optimal UX */}
     89 + <Icon kind='arrowDown' width={14} height={8} color='#8E8AA0' className={styles.arrow} />
     90 + </button>
     91 + </div>
     92 + 
     93 + <div className={styles.desc}>
     94 + The Lodash library exported as ES modules. Generated using lodash-cli
     95 + </div>
     96 + </header>
     97 + 
     98 + <CSSTransition
     99 + in={open}
     100 + timeout={600}
     101 + classNames={{
     102 + enterDone: styles.contentEnterDone,
     103 + }}
     104 + >
     105 + <div className={styles.content}>
     106 + <div className={styles.contentInner}>
     107 + <div className={styles.stat}>
     108 + <div className={styles.statHeader}>
     109 + <Icon kind='script' color='#8E8AA0' className={styles.statIcon} />
     110 + Script
     111 + </div>
     112 + <a href='#' className={styles.statLink} target='_blank' rel='noreferrer'>
     113 + /rsrc.php/v3id044/yu/l/en_US/yD2XaVkWQHO.js?_nc_x=Ij3Wp8lg5Kz
     114 + </a>
     115 + </div>
     116 + 
     117 + <div className={styles.statList}>
     118 + <div className={clsx(styles.stat, styles.statListItemSmall)}>
     119 + <div className={styles.statHeader}>
     120 + <Icon kind='license' color='#8E8AA0' className={styles.statIcon} />
     121 + License
     122 + </div>
     123 + <div className={styles.statTitle}>MIT license</div>
     124 + <div className={styles.statSubtitle}>freely distributable</div>
     125 + </div>
     126 + 
     127 + <div className={clsx(styles.stat, styles.statListItemSmall)}>
     128 + <div className={styles.statHeader}>
     129 + <Icon kind='rating' color='#8E8AA0' className={styles.statIcon} />
     130 + Rating
     131 + </div>
     132 + <div className={styles.statTitle}>
     133 + 385
     134 + {/* or: <div className={clsx(styles.statRating, styles.statRatingRed)}> */}
     135 + <div className={clsx(styles.statRating, styles.statRatingGreen)}>
     136 + <Icon
     137 + kind='ratingArrow'
     138 + width={12}
     139 + height={12}
     140 + className={styles.statRatingArrow}
     141 + />
     142 + +4
     143 + </div>
     144 + </div>
     145 + <div className={styles.statSubtitle}>out of 12 842</div>
     146 + </div>
     147 + 
     148 + <div className={clsx(styles.stat, styles.statListItemLarge)}>
     149 + <div className={styles.statHeader}>
     150 + <Icon kind='dependency' color='#8E8AA0' className={styles.statIcon} />4 Dependency
     151 + </div>
     152 + <ChipGroup
     153 + chips={['art', 'create-react-class', 'loose-envify', 'scheduler']}
     154 + fontSize='small'
     155 + />
     156 + </div>
     157 + </div>
     158 + 
     159 + <div className={styles.stat}>
     160 + <div className={styles.statHeader}>
     161 + <Icon kind='graph' color='#8E8AA0' className={styles.statIcon} />
     162 + Versions popularity by uses on sites
     163 + </div>
     164 + 
     165 + <div className={styles.popularity}>
     166 + <div className={styles.popularityItemWrapper}>
     167 + <div className={styles.popularityItem}>
     168 + <div className={styles.popularityFill} style={{ height: '100%' }}>
     169 + 89 912
     170 + </div>
     171 + </div>
     172 + 
     173 + <div className={styles.popularityVersion}>21.3.0</div>
     174 + </div>
     175 + 
     176 + <div className={styles.popularityItemWrapper}>
     177 + <div className={styles.popularityItem}>
     178 + <div
     179 + className={clsx(styles.popularityFill, styles.popularityFillAccent)}
     180 + style={{ height: '90%' }}
     181 + >
     182 + 67 111
     183 + </div>
     184 + </div>
     185 + 
     186 + <div className={styles.popularityVersion}>18.2.0</div>
     187 + </div>
     188 + 
     189 + <div className={styles.popularityItemWrapper}>
     190 + <div className={styles.popularityItem}>
     191 + <div className={styles.popularityFill} style={{ height: '80%' }}>
     192 + 44 212
     193 + </div>
     194 + </div>
     195 + 
     196 + <div className={styles.popularityVersion}>20.1.0</div>
     197 + </div>
     198 + 
     199 + <div className={styles.popularityItemWrapper}>
     200 + <div className={styles.popularityItem}>
     201 + <div className={styles.popularityFill} style={{ height: '70%' }}>
     202 + 41 129
     203 + </div>
     204 + </div>
     205 + 
     206 + <div className={styles.popularityVersion}>18.0.0</div>
     207 + </div>
     208 + 
     209 + <div className={styles.popularityItemWrapper}>
     210 + <div className={styles.popularityItem}>
     211 + <div className={styles.popularityFill} style={{ height: '60%' }}>
     212 + 40 465
     213 + </div>
     214 + </div>
     215 + 
     216 + <div className={styles.popularityVersion}>19.11.2</div>
     217 + </div>
     218 + 
     219 + <div className={styles.popularityItemWrapper}>
     220 + <div className={styles.popularityItem}>
     221 + <div className={styles.popularityFill} style={{ height: '50%' }}>
     222 + 38 907
     223 + </div>
     224 + </div>
     225 + 
     226 + <div className={styles.popularityVersion}>
     227 + 8.1.2
     228 + <Icon
     229 + kind='bugOutlined'
     230 + color='#212121'
     231 + className={styles.popularityVersionIcon}
     232 + />
     233 + </div>
     234 + </div>
     235 + </div>
     236 + </div>
     237 + 
     238 + {/* TODO: add Modules treemap here */}
     239 + 
     240 + <div className={styles.stat}>
     241 + <div className={styles.statHeader}>Used on</div>
     242 + 
     243 + <SitesList sites={sites} />
     244 + </div>
     245 + 
     246 + <div className={styles.actions}>
     247 + <div className={styles.links}>
     248 + <a href='#' className={styles.link} target='_blank' rel='noreferrer'>
     249 + <Icon kind='repository' color='#212121' className={styles.linkIcon} />
     250 + Repository
     251 + </a>
     252 + 
     253 + <a href='#' className={styles.link} target='_blank' rel='noreferrer'>
     254 + <Icon kind='link' color='#212121' className={styles.linkIcon} />
     255 + Homepage
     256 + </a>
     257 + 
     258 + <a href='#' className={styles.link} target='_blank' rel='noreferrer'>
     259 + <Icon
     260 + kind='npm'
     261 + width={32}
     262 + height={32}
     263 + color='#212121'
     264 + className={styles.linkIcon}
     265 + />
     266 + </a>
     267 + </div>
     268 + 
     269 + <Button variant='arrow'>Details</Button>
     270 + </div>
     271 + </div>
     272 + </div>
     273 + </CSSTransition>
     274 + 
     275 + <footer className={styles.footer}>
     276 + <div className={styles.tags}>
     277 + <a href='#' className={styles.tag}>
     278 + #moment
     279 + </a>
     280 + <a href='#' className={styles.tag}>
     281 + #date
     282 + </a>
     283 + <a href='#' className={styles.tag}>
     284 + #time
     285 + </a>
     286 + <a href='#' className={styles.tag}>
     287 + #parse
     288 + </a>
     289 + <a href='#' className={styles.tag}>
     290 + #format
     291 + </a>
     292 + <Chip variant='blueAdditional' size='medium' fontWeight={500}>
     293 + +45
     294 + </Chip>
     295 + </div>
     296 + 
     297 + <div className={styles.author}>
     298 + <span className={styles.authorName}>jdalton</span>
     299 + <img className={styles.authorImage} src='https://via.placeholder.com/36' alt='' />
     300 + </div>
     301 + </footer>
     302 + </div>
     303 + );
     304 +}
     305 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/SearchBar/SearchBar.module.scss
     1 +@import '~styles/_vars.scss';
     2 +@import '~styles/responsive.scss';
     3 + 
     4 +.searchBar {
     5 + position: relative;
     6 +}
     7 + 
     8 +.input {
     9 + display: inline-block;
     10 + vertical-align: middle;
     11 + font-family: inherit;
     12 + appearance: textfield;
     13 + width: 100%;
     14 + font-size: 19px;
     15 + line-height: 26px;
     16 + padding: 15px 56px 15px 28px;
     17 + color: $black;
     18 + border: none;
     19 + background: $gray-surface;
     20 + border-radius: 30px;
     21 + outline: none;
     22 + white-space: nowrap;
     23 + text-overflow: ellipsis;
     24 + overflow: hidden;
     25 + 
     26 + &::placeholder {
     27 + color: $gray-text;
     28 + }
     29 + 
     30 + @include mobile-and-tablet {
     31 + padding: 15px 50px 15px 24px;
     32 + font-size: 16px;
     33 + line-height: 22px;
     34 + }
     35 +}
     36 + 
     37 +.submit,
     38 +.clear {
     39 + cursor: pointer;
     40 + position: absolute;
     41 + top: 16px;
     42 + right: 16px;
     43 + width: 24px;
     44 + height: 24px;
     45 + border: none;
     46 + background: transparent;
     47 + user-select: none;
     48 + display: flex;
     49 + align-items: center;
     50 + justify-content: center;
     51 + 
     52 + @include mobile-and-tablet {
     53 + top: 14px;
     54 + right: 14px;
     55 + }
     56 +}
     57 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/SearchBar/SearchBar.tsx
     1 +import React, { useState } from 'react';
     2 +import styles from './SearchBar.module.scss';
     3 +import { Icon } from '../Icon/Icon';
     4 + 
     5 +type Props = {
     6 + value?: string;
     7 +};
     8 + 
     9 +export default function SearchBar({ value }: Props) {
     10 + // FIXME: not sure that this is legal
     11 + const [inputText, setInputText] = useState<string | undefined>(value);
     12 + 
     13 + const changeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
     14 + setInputText(e.target.value);
     15 + };
     16 + 
     17 + const clearHandler = () => {
     18 + setInputText('');
     19 + };
     20 + 
     21 + return (
     22 + <div className={styles.searchBar}>
     23 + <input
     24 + type='text'
     25 + className={styles.input}
     26 + value={inputText}
     27 + onChange={changeHandler}
     28 + placeholder='Start analyzing...'
     29 + />
     30 + {inputText ? (
     31 + <button type='button' className={styles.clear} onClick={clearHandler}>
     32 + <Icon kind='cross' width={24} height={24} color='#8E8AA0' />
     33 + </button>
     34 + ) : (
     35 + <button type='submit' className={styles.submit}>
     36 + <Icon kind='arrow' width={9} height={18} stroke='#8E8AA0' />
     37 + </button>
     38 + )}
     39 + </div>
     40 + );
     41 +}
     42 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/SitesList/SitesList.module.scss
     1 +@import '~styles/_vars.scss';
     2 +@import '~styles/responsive.scss';
     3 + 
     4 +.sitesList {
     5 + display: flex;
     6 + flex-wrap: nowrap;
     7 + align-items: center;
     8 + justify-content: space-between;
     9 + overflow-x: auto;
     10 + 
     11 + &::-webkit-scrollbar {
     12 + display: none;
     13 + }
     14 + 
     15 + @include mobile-and-tablet {
     16 + justify-content: flex-start;
     17 + }
     18 + 
     19 + & > * + * {
     20 + margin-left: 40px;
     21 + }
     22 +}
     23 + 
     24 +.site {
     25 + display: flex;
     26 + align-items: center;
     27 + 
     28 + @include mobile-and-tablet {
     29 + min-width: 145px;
     30 + }
     31 +}
     32 + 
     33 +.imageWrapper {
     34 + display: flex;
     35 + flex-shrink: 0;
     36 + width: 44px;
     37 + margin-right: 14px;
     38 +}
     39 + 
     40 +.image {
     41 + width: 100%;
     42 + object-fit: cover;
     43 +}
     44 + 
     45 +.title {
     46 + font-weight: 500;
     47 +}
     48 + 
     49 +.subtitle {
     50 + font-size: 14px;
     51 + line-height: 22px;
     52 + color: $gray-text;
     53 +}
     54 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/SitesList/SitesList.tsx
     1 +import React from 'react';
     2 +import styles from './SitesList.module.scss';
     3 + 
     4 +export type Site = {
     5 + id: string;
     6 + image: string;
     7 + name: string;
     8 + packagesCount: number;
     9 +};
     10 + 
     11 +type Props = {
     12 + sites: Site[];
     13 +};
     14 + 
     15 +function Site({ image, name, packagesCount }: Site) {
     16 + return (
     17 + <div className={styles.site}>
     18 + <div className={styles.imageWrapper}>
     19 + <img src={image} className={styles.image} alt='' />
     20 + </div>
     21 + <div className={styles.content}>
     22 + <div className={styles.title}>{name}</div>
     23 + <div className={styles.subtitle}>{packagesCount} packages</div>
     24 + </div>
     25 + </div>
     26 + );
     27 +}
     28 + 
     29 +export default function SitesList({ sites }: Props) {
     30 + return (
     31 + <div className={styles.sitesList}>
     32 + {sites.map((site) => (
     33 + <Site key={site.id} {...site} />
     34 + ))}
     35 + </div>
     36 + );
     37 +}
     38 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/styles/_vars.scss
    skipped 7 lines
    8 8   
    9 9  $blue-accent: #4549ff;
    10 10  $blue-secondary: #a2a4ff;
     11 +$blue-additional: #ededff;
    11 12  $red-accent: #f3512e;
    12 13  $red-secondary: #f9a896;
     14 +$red-additional: #feeeeb;
    13 15  $orange-accent: #f3812e;
    14 16  $orange-secondary: #f9bf96;
     17 +$orange-additional: #fef3eb;
    15 18  $yellow-accent: #f1ce61;
    16 19  $yellow-secondary: #f8e6af;
     20 +$yellow-additional: #fcf5df;
    17 21  $green-accent: #49d581;
    18 22  $green-secondary: #a4eabf;
     23 +$green-additional: #e4f9ec;
    19 24   
    20 25  $black: #212121;
    21 26  $gray-text: #8e8aa0;
    skipped 4 lines
    26 31  $shadow: 0px 1px 35px rgba(#5c54ba, 0.1);
    27 32  $shadow-small: 0px 1px 24px rgba(#5c54ba, 0.1);
    28 33   
     34 +$transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
     35 +$transition-duration: 600ms;
     36 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/styles/responsive.scss
    1 1  $breakpoint-mobile: 575px;
     2 +$breakpoint-sm: 767px;
    2 3  $breakpoint-tablet: 991px;
    3 4  $breakpoint-lg: 1199px;
    4 5  $breakpoint-xl: 1399px;
    skipped 12 lines
    17 18   
    18 19  @mixin mobile-and-tablet {
    19 20   @media (max-width: $breakpoint-tablet) {
     21 + @content;
     22 + }
     23 +}
     24 + 
     25 +@mixin sm {
     26 + @media (max-width: $breakpoint-sm) {
    20 27   @content;
    21 28   }
    22 29  }
    skipped 7 lines
  • ■ ■ ■ ■ ■ ■
    yarn.lock
    skipped 1718 lines
    1719 1719   dependencies:
    1720 1720   regenerator-runtime "^0.13.4"
    1721 1721   
    1722  -"@babel/runtime@^7.12.1", "@babel/runtime@^7.9.0", "@babel/runtime@^7.9.2":
     1722 +"@babel/runtime@^7.12.1", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.0", "@babel/runtime@^7.9.2":
    1723 1723   version "7.18.9"
    1724 1724   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a"
    1725 1725   integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==
    skipped 2181 lines
    3907 3907   version "11.0.5"
    3908 3908   resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.5.tgz#0d546261b4021e1f9d85b50401c0a42acb106087"
    3909 3909   integrity sha512-VIOi9i2Oj5XsmWWoB72p3KlZoEbdRAcechJa8Ztebw7bDl2YmR+odxIqhtJGp1q2EozHs02US+gzxJ9nuf56qg==
     3910 + dependencies:
     3911 + "@types/react" "*"
     3912 + 
     3913 +"@types/react-transition-group@^4.4.5":
     3914 + version "4.4.5"
     3915 + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416"
     3916 + integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==
    3910 3917   dependencies:
    3911 3918   "@types/react" "*"
    3912 3919   
    skipped 2863 lines
    6776 6783   dependencies:
    6777 6784   utila "~0.4"
    6778 6785   
     6786 +dom-helpers@^5.0.1:
     6787 + version "5.2.1"
     6788 + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
     6789 + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
     6790 + dependencies:
     6791 + "@babel/runtime" "^7.8.7"
     6792 + csstype "^3.0.2"
     6793 + 
    6779 6794  dom-serializer@0:
    6780 6795   version "0.2.2"
    6781 6796   resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
    skipped 5353 lines
    12135 12150   kleur "^3.0.3"
    12136 12151   sisteransi "^1.0.5"
    12137 12152   
    12138  -prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.7.2, prop-types@^15.8.1:
     12153 +prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
    12139 12154   version "15.8.1"
    12140 12155   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
    12141 12156   integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
    skipped 405 lines
    12547 12562   "@babel/runtime" "^7.10.2"
    12548 12563   use-composed-ref "^1.0.0"
    12549 12564   use-latest "^1.0.0"
     12565 + 
     12566 +react-transition-group@^4.4.5:
     12567 + version "4.4.5"
     12568 + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
     12569 + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
     12570 + dependencies:
     12571 + "@babel/runtime" "^7.5.5"
     12572 + dom-helpers "^5.0.1"
     12573 + loose-envify "^1.4.0"
     12574 + prop-types "^15.6.2"
    12550 12575   
    12551 12576  react@^17.0.2:
    12552 12577   version "17.0.2"
    skipped 3007 lines
Please wait...
Page is in error, reload to recover