Projects STRLCPY gradejs Commits bfb549eb
🤬
  • wip: add sticky header and tooltip, mild refactor for home and search page components (#63)

    * wip: add sticky header, mild PackagePreview refactor
    
    * wip: add Tooltip component to rating title on PackagePreview via react-popper-tooltip
    
    * wip: refactor cards, card lists, avatars, chips, chip groups
    
    * fix: refactor/fixes due to review
    
    * fix: explicit fallback to undefined when to safeguard against possible false or '' prop values
  • Loading...
  • Dmitry Shakun committed with GitHub 2 years ago
    bfb549eb
    1 parent f0d57cc4
  • ■ ■ ■ ■ ■ ■
    packages/web/package.json
    skipped 30 lines
    31 31   "@storybook/builder-webpack5": "^6.3.8",
    32 32   "@storybook/manager-webpack5": "^6.3.8",
    33 33   "@storybook/react": "^6.3.8",
     34 + "@types/lodash.throttle": "^4.1.7",
    34 35   "@types/react": "^17.0.21",
    35 36   "@types/react-dom": "^17.0.9",
    36 37   "@types/react-gtm-module": "^2.0.1",
    skipped 31 lines
    68 69   "clsx": "^1.1.1",
    69 70   "express": "^4.18.1",
    70 71   "lodash.memoize": "^4.1.2",
     72 + "lodash.throttle": "^4.1.1",
    71 73   "plausible-tracker": "^0.3.8",
    72 74   "react": "^17.0.2",
    73 75   "react-dom": "^17.0.2",
    74 76   "react-ga": "^3.3.0",
    75 77   "react-helmet": "^6.1.0",
    76 78   "react-hook-form": "^7.15.3",
     79 + "react-popper-tooltip": "^4.4.2",
    77 80   "react-redux": "^8.0.2",
    78 81   "react-router-dom": "^6.3.0",
    79 82   "react-top-loading-bar": "^2.3.1",
    skipped 5 lines
  • packages/web/src/assets/icons/sprite/question-mark.svg
  • packages/web/src/assets/icons/sprite/bug.svg packages/web/src/assets/icons/sprite/vulnerability.svg
  • ■ ■ ■ ■ ■
    packages/web/src/components/layouts/Error/Error.tsx
    skipped 1 lines
    2 2  import styles from './Error.module.scss';
    3 3  import Container from 'components/ui/Container/Container';
    4 4  import SearchBar from '../../ui/SearchBar/SearchBar';
    5  -import { CardProps } from '../../ui/Card/Card';
    6 5  import CardGroup from '../../ui/CardGroup/CardGroup';
    7  -import CardList from '../../ui/CardList/CardList';
    8 6  import CardGroups from '../../ui/CardGroups/CardGroups';
    9 7  import Footer from '../../ui/Footer/Footer';
    10  -import ErrorHeader from 'components/ui/Header/ErrorHeader';
     8 +import StickyErrorHeader from '../../ui/Header/StickyErrorHeader';
     9 +import PackagesBySourceCardList from '../../ui/CardList/PackagesBySourceCardList';
     10 +import PopularPackageCardList from '../../ui/CardList/PopularPackageCardList';
     11 +import { packagesBySourceListData, popularPackageListData } from '../../../mocks/CardListsMocks';
    11 12   
    12 13  export type Props = {
    13 14   host: string;
    skipped 8 lines
    22 23   action = 'GradeJS will analyze production JavaScript files and match webpack bundled modules to 1,826 indexed NPM libraries over 54,735 releases',
    23 24   actionTitle,
    24 25  }: Props) {
    25  - // TODO: mock data, remove later
    26  - const similarCards: CardProps[] = [
    27  - {
    28  - id: 'uExBVGuF',
    29  - title: 'github.com',
    30  - icon: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    31  - packageTags: {
    32  - featuredPackages: ['mdast-util-from-markdown', 'react', 'react-dom'],
    33  - restPackages: 45,
    34  - },
    35  - },
    36  - {
    37  - id: '1EkL1u5g',
    38  - title: 'fingerprint.com',
    39  - icon: 'https://avatars.githubusercontent.com/u/67208791?s=200&v=4',
    40  - packageTags: {
    41  - featuredPackages: ['mdast-util-from-markdown', 'react', 'react-dom'],
    42  - restPackages: 45,
    43  - },
    44  - },
    45  - {
    46  - id: 'mhwO2bPM',
    47  - title: 'facebook.com',
    48  - icon: 'https://avatars.githubusercontent.com/u/69631?s=200&v=4',
    49  - packageTags: {
    50  - featuredPackages: ['react'],
    51  - restPackages: 45,
    52  - },
    53  - },
    54  - ];
    55  - 
    56  - // TODO: mock data, remove later
    57  - const popularPackages: CardProps[] = [
    58  - {
    59  - id: 'FPsBcl8R',
    60  - title: '@team-griffin/react-heading-section',
    61  - description: "This package's job is to automatically determine...",
    62  - featuredSites: {
    63  - iconList: [
    64  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    65  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    66  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    67  - ],
    68  - numberOfUses: 5265,
    69  - },
    70  - },
    71  - {
    72  - id: 'emtYcsUh',
    73  - title: 'unist-util-generated',
    74  - description: 'unist utility to check if a node is generated',
    75  - featuredSites: {
    76  - iconList: [
    77  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    78  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    79  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    80  - ],
    81  - numberOfUses: 5265,
    82  - },
    83  - },
    84  - {
    85  - id: 'TYIwvAfy',
    86  - title: 'react-smooth',
    87  - description: 'is a animation library work on React',
    88  - featuredSites: {
    89  - iconList: [
    90  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    91  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    92  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    93  - ],
    94  - numberOfUses: 5265,
    95  - },
    96  - },
    97  - {
    98  - id: 'Lq1pEEX7',
    99  - title: 'unist-util-position',
    100  - description: 'unist utility to get the positional info of nodes',
    101  - featuredSites: {
    102  - iconList: [
    103  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    104  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    105  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    106  - ],
    107  - numberOfUses: 5265,
    108  - },
    109  - },
    110  - {
    111  - id: 'cWOgIbmp',
    112  - title: 'vfile-message',
    113  - description: 'Create vfile messages',
    114  - featuredSites: {
    115  - iconList: [
    116  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    117  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    118  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    119  - ],
    120  - numberOfUses: 5265,
    121  - },
    122  - },
    123  - {
    124  - id: 'UT97Vpoi',
    125  - title: 'Go to all Popular packages',
    126  - variant: 'toAll',
    127  - },
    128  - ];
    129  - 
    130 26   return (
    131 27   <>
    132  - <ErrorHeader />
     28 + <StickyErrorHeader />
    133 29   
    134 30   <Container>
    135 31   <section className={styles.errorPage}>
    skipped 12 lines
    148 44   Feels like these <CardList/>'s should be separate components. */}
    149 45   <CardGroups>
    150 46   <CardGroup title='But we have'>
    151  - <CardList cards={similarCards} />
     47 + <PackagesBySourceCardList cards={packagesBySourceListData} />
    152 48   </CardGroup>
    153 49   
    154 50   <CardGroup title='Popular packages'>
    155  - <CardList cards={popularPackages} />
     51 + <PopularPackageCardList cards={popularPackageListData} />
    156 52   </CardGroup>
    157 53   </CardGroups>
    158 54   </Container>
    skipped 6 lines
  • ■ ■ ■ ■ ■
    packages/web/src/components/layouts/Home/Home.tsx
    1 1  import React from 'react';
    2  -import { CardProps } from '../../ui/Card/Card';
    3 2  import Hero from '../../ui/Hero/Hero';
    4 3  import Container from 'components/ui/Container/Container';
    5  -import CardList from '../../ui/CardList/CardList';
    6 4  import CardGroups from '../../ui/CardGroups/CardGroups';
    7  -import Footer from '../../ui/Footer/Footer';
    8 5  import CardGroup from '../../ui/CardGroup/CardGroup';
     6 +import PopularPackageCardList from '../../ui/CardList/PopularPackageCardList';
     7 +import PackagesBySourceCardList from '../../ui/CardList/PackagesBySourceCardList';
     8 +import Footer from '../../ui/Footer/Footer';
     9 +import {
     10 + packagesBySourceListData,
     11 + popularPackageListData,
     12 + scansWithVulnerabilitiesListData,
     13 +} from '../../../mocks/CardListsMocks';
     14 +import ScansWithVulnerabilitiesCardList from 'components/ui/CardList/ScansWithVulnerabilitiesCardList';
    9 15   
    10 16  type Props = {
    11 17   loading?: boolean;
    skipped 2 lines
    14 20  };
    15 21   
    16 22  export default function Home({ suggestions, loading, onSubmit }: Props) {
    17  - // TODO: mock data, remove later
    18  - const popularCards: CardProps[] = [
    19  - {
    20  - id: 'uExBVGuF',
    21  - title: 'github.com',
    22  - icon: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    23  - packageTags: {
    24  - featuredPackages: ['mdast-util-from-markdown', 'react', 'react-dom'],
    25  - restPackages: 45,
    26  - },
    27  - },
    28  - {
    29  - id: '1EkL1u5g',
    30  - title: 'fingerprint.com',
    31  - icon: 'https://avatars.githubusercontent.com/u/67208791?s=200&v=4',
    32  - packageTags: {
    33  - featuredPackages: ['mdast-util-from-markdown', 'react', 'react-dom'],
    34  - restPackages: 45,
    35  - },
    36  - },
    37  - {
    38  - id: 'mhwO2bPM',
    39  - title: 'facebook.com',
    40  - icon: 'https://avatars.githubusercontent.com/u/69631?s=200&v=4',
    41  - packageTags: {
    42  - featuredPackages: ['react'],
    43  - restPackages: 45,
    44  - },
    45  - },
    46  - ];
    47  - 
    48  - // TODO: mock data, remove later
    49  - const popularPackages: CardProps[] = [
    50  - {
    51  - id: 'FPsBcl8R',
    52  - title: '@team-griffin/react-heading-section',
    53  - description: "This package's job is to automatically determine...",
    54  - featuredSites: {
    55  - iconList: [
    56  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    57  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    58  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    59  - ],
    60  - numberOfUses: 5265,
    61  - },
    62  - },
    63  - {
    64  - id: 'emtYcsUh',
    65  - title: 'unist-util-generated',
    66  - description: 'unist utility to check if a node is generated',
    67  - featuredSites: {
    68  - iconList: [
    69  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    70  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    71  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    72  - ],
    73  - numberOfUses: 5265,
    74  - },
    75  - },
    76  - {
    77  - id: 'TYIwvAfy',
    78  - title: 'react-smooth',
    79  - description: 'is a animation library work on React',
    80  - featuredSites: {
    81  - iconList: [
    82  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    83  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    84  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    85  - ],
    86  - numberOfUses: 5265,
    87  - },
    88  - },
    89  - {
    90  - id: 'Lq1pEEX7',
    91  - title: 'unist-util-position',
    92  - description: 'unist utility to get the positional info of nodes',
    93  - featuredSites: {
    94  - iconList: [
    95  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    96  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    97  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    98  - ],
    99  - numberOfUses: 5265,
    100  - },
    101  - },
    102  - {
    103  - id: 'cWOgIbmp',
    104  - title: 'vfile-message',
    105  - description: 'Create vfile messages',
    106  - featuredSites: {
    107  - iconList: [
    108  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    109  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    110  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    111  - ],
    112  - numberOfUses: 5265,
    113  - },
    114  - },
    115  - {
    116  - id: 'UT97Vpoi',
    117  - title: 'Go to all Popular packages',
    118  - variant: 'toAll',
    119  - },
    120  - ];
    121  - 
    122  - // TODO: mock data, remove later
    123  - const vulnerablePackages: CardProps[] = [
    124  - {
    125  - id: 'LnO9Xynn',
    126  - title: 'disneyland.omsk.ru/signup',
    127  - vulnerablePackage: {
    128  - name: 'mdast-util-from-markdown',
    129  - },
    130  - variant: 'vulnerable',
    131  - },
    132  - {
    133  - id: '-A74UAy8',
    134  - title: 'disneyland.omsk.ru/signup',
    135  - vulnerablePackage: {
    136  - name: 'mdast-util-from-markdown',
    137  - moreCount: 1,
    138  - },
    139  - variant: 'vulnerable',
    140  - },
    141  - {
    142  - id: 'DPa05I2W',
    143  - title: 'disneyland.omsk.ru/signup',
    144  - vulnerablePackage: {
    145  - name: 'mdast-util-from-markdown',
    146  - },
    147  - variant: 'vulnerable',
    148  - },
    149  - ];
    150  - 
     23 + // TODO: replace mock data with real one
    151 24   return (
    152 25   <>
    153 26   <Hero suggestions={suggestions} onSubmit={onSubmit} loading={loading} />
    154 27   
    155 28   <Container>
    156  - {/* TODO: Trying to fit separate domain entities within a single component seems like burden.
    157  - Feels like these <CardList/>'s should be separate components. */}
    158 29   <CardGroups>
    159 30   <CardGroup title='Popular search queries'>
    160  - <CardList cards={popularCards} />
     31 + <PackagesBySourceCardList cards={packagesBySourceListData} />
    161 32   </CardGroup>
    162 33   
    163 34   <CardGroup title='Popular packages'>
    164  - <CardList cards={popularPackages} />
     35 + <PopularPackageCardList cards={popularPackageListData} />
    165 36   </CardGroup>
    166 37   
    167 38   <CardGroup title='Vulnerable sites'>
    168  - <CardList cards={vulnerablePackages} variant='vertical' />
     39 + <ScansWithVulnerabilitiesCardList cards={scansWithVulnerabilitiesListData} />
    169 40   </CardGroup>
    170 41   
    171 42   <CardGroup title='Authors of popular packages'>
    172  - <CardList cards={popularCards} />
     43 + <PackagesBySourceCardList cards={packagesBySourceListData} />
    173 44   </CardGroup>
    174 45   </CardGroups>
    175 46   </Container>
    skipped 6 lines
  • ■ ■ ■ ■ ■
    packages/web/src/components/layouts/SearchResults/SearchResults.tsx
    skipped 4 lines
    5 5  import { Icon } from '../../ui/Icon/Icon';
    6 6  import PackagePreview from '../../ui/PackagePreview/PackagePreview';
    7 7  import SearchedResource from '../../ui/SearchedResource/SearchedResource';
    8  -import { CardProps } from '../../ui/Card/Card';
    9 8  import CardGroup from '../../ui/CardGroup/CardGroup';
    10  -import CardList from '../../ui/CardList/CardList';
    11 9  import CardGroups from 'components/ui/CardGroups/CardGroups';
    12 10  import LoadingBar, { LoadingBarRef } from 'react-top-loading-bar';
    13  -import DefaultHeader from '../../ui/Header/DefaultHeader';
    14 11  import SearchResultsSidebar from 'components/ui/SearchResultsSidebar/SearchResultsSidebar';
    15 12  import { SearchedResourceSkeleton } from '../../ui/SearchedResource/SearchedResourceSkeleton';
    16 13  import { PackagePreviewSkeleton } from '../../ui/PackagePreview/PackagePreviewSkeleton';
    17 14  import { CardListSkeleton } from '../../ui/CardList/CardListSkeleton';
     15 +import StickyDefaultHeader from '../../ui/Header/StickyDefaultHeader';
     16 +import PackagesBySourceCardList from '../../ui/CardList/PackagesBySourceCardList';
     17 +import PopularPackageCardList from '../../ui/CardList/PopularPackageCardList';
     18 +import { packagesBySourceListData, popularPackageListData } from '../../../mocks/CardListsMocks';
    18 19   
    19 20  type Props = {
    20 21   pageLoading?: boolean;
    skipped 20 lines
    41 42   }, []);
    42 43   
    43 44   // TODO: mock data, remove later
    44  - const similarCards: CardProps[] = [
    45  - {
    46  - id: 'uExBVGuF',
    47  - title: 'github.com',
    48  - icon: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    49  - packageTags: {
    50  - featuredPackages: ['mdast-util-from-markdown', 'react', 'react-dom'],
    51  - restPackages: 45,
    52  - },
    53  - },
    54  - {
    55  - id: '1EkL1u5g',
    56  - title: 'fingerprint.com',
    57  - icon: 'https://avatars.githubusercontent.com/u/67208791?s=200&v=4',
    58  - packageTags: {
    59  - featuredPackages: ['mdast-util-from-markdown', 'react', 'react-dom'],
    60  - restPackages: 45,
    61  - },
    62  - },
    63  - {
    64  - id: 'mhwO2bPM',
    65  - title: 'facebook.com',
    66  - icon: 'https://avatars.githubusercontent.com/u/69631?s=200&v=4',
    67  - packageTags: {
    68  - featuredPackages: ['react'],
    69  - restPackages: 45,
    70  - },
    71  - },
    72  - ];
    73  - 
    74  - // TODO: mock data, remove later
    75  - const popularPackages: CardProps[] = [
    76  - {
    77  - id: 'FPsBcl8R',
    78  - title: '@team-griffin/react-heading-section',
    79  - description: "This package's job is to automatically determine...",
    80  - featuredSites: {
    81  - iconList: [
    82  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    83  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    84  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    85  - ],
    86  - numberOfUses: 5265,
    87  - },
    88  - },
    89  - {
    90  - id: 'emtYcsUh',
    91  - title: 'unist-util-generated',
    92  - description: 'unist utility to check if a node is generated',
    93  - featuredSites: {
    94  - iconList: [
    95  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    96  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    97  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    98  - ],
    99  - numberOfUses: 5265,
    100  - },
    101  - },
    102  - {
    103  - id: 'TYIwvAfy',
    104  - title: 'react-smooth',
    105  - description: 'is a animation library work on React',
    106  - featuredSites: {
    107  - iconList: [
    108  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    109  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    110  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    111  - ],
    112  - numberOfUses: 5265,
    113  - },
    114  - },
    115  - {
    116  - id: 'Lq1pEEX7',
    117  - title: 'unist-util-position',
    118  - description: 'unist utility to get the positional info of nodes',
    119  - featuredSites: {
    120  - iconList: [
    121  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    122  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    123  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    124  - ],
    125  - numberOfUses: 5265,
    126  - },
    127  - },
    128  - {
    129  - id: 'cWOgIbmp',
    130  - title: 'vfile-message',
    131  - description: 'Create vfile messages',
    132  - featuredSites: {
    133  - iconList: [
    134  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    135  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    136  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    137  - ],
    138  - numberOfUses: 5265,
    139  - },
    140  - },
    141  - {
    142  - id: 'UT97Vpoi',
    143  - title: 'Go to all Popular packages',
    144  - variant: 'toAll',
    145  - },
    146  - ];
    147  - 
    148 45   const metaItems = [
    149 46   {
    150 47   icon: <Icon kind='weight' width={24} height={24} />,
    skipped 4 lines
    155 52   text: '50 scripts found',
    156 53   },
    157 54   {
    158  - icon: <Icon kind='bug' width={24} height={24} color='#F3512E' />,
     55 + icon: <Icon kind='vulnerability' width={24} height={24} color='#F3512E' />,
    159 56   text: '6 vulnerabilities in 4 packages',
    160 57   },
    161 58   {
    skipped 28 lines
    190 87   />
    191 88   )}
    192 89   
    193  - <DefaultHeader showSearch />
     90 + <StickyDefaultHeader showSearch />
    194 91   
    195 92   <Container>
    196 93   <div className={styles.searchResults}>
    skipped 27 lines
    224 121   {loading ? (
    225 122   <PackagePreviewSkeleton />
    226 123   ) : (
    227  - <PackagePreview name='@team-griffin/react-heading-section' version='3.0.0 - 4.16.4' />
     124 + <PackagePreview
     125 + name='@team-griffin/react-heading-section'
     126 + version='3.0.0 - 4.16.4'
     127 + desc='The Lodash library exported as ES modules. Generated using lodash-cli'
     128 + problems={['vulnerabilities']}
     129 + keywords={['#moment', '#date', '#time', '#parse', '#format', '#format', '#format']}
     130 + author={{ name: 'jdalton', image: 'https://via.placeholder.com/36' }}
     131 + />
    228 132   )}
    229 133   
    230 134   {loading ? (
    231 135   <PackagePreviewSkeleton />
    232 136   ) : (
    233  - <PackagePreview name='@team-griffin/react-heading-section' version='3.0.0 - 4.16.4' />
     137 + <PackagePreview
     138 + name='@team-griffin/react-heading-section'
     139 + version='3.0.0 - 4.16.4'
     140 + desc='The Lodash library exported as ES modules. Generated using lodash-cli'
     141 + problems={['vulnerabilities', 'duplicate', 'outdated']}
     142 + keywords={['#moment', '#date', '#time', '#parse', '#format']}
     143 + author={{ name: 'jdalton', image: 'https://via.placeholder.com/36' }}
     144 + />
    234 145   )}
    235 146   </div>
    236 147   </div>
    237 148   
    238  - {/* TODO: Trying to fit separate domain entities within a single component seems like burden.
    239  - Feels like these <CardList/>'s should be separate components. */}
    240 149   <CardGroups>
    241 150   <CardGroup title='Similar sites'>
    242  - {loading ? <CardListSkeleton /> : <CardList cards={similarCards} />}
     151 + {loading ? (
     152 + <CardListSkeleton />
     153 + ) : (
     154 + <PackagesBySourceCardList cards={packagesBySourceListData} />
     155 + )}
    243 156   </CardGroup>
    244 157   
    245 158   <CardGroup title='Popular packages'>
    246  - {loading ? <CardListSkeleton /> : <CardList cards={popularPackages} />}
     159 + {loading ? (
     160 + <CardListSkeleton numberOfElements={6} />
     161 + ) : (
     162 + <PopularPackageCardList cards={popularPackageListData} />
     163 + )}
    247 164   </CardGroup>
    248 165   </CardGroups>
    249 166   </Container>
    skipped 6 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Avatar/Avatar.module.scss
     1 +@import '~styles/_vars.scss';
     2 +@import '~styles/responsive.scss';
     3 + 
     4 +.avatarItem {
     5 + width: 32px;
     6 + height: 32px;
     7 + border: 2px solid $white;
     8 + border-radius: 50%;
     9 + overflow: hidden;
     10 + flex-shrink: 0;
     11 + display: flex;
     12 + align-items: center;
     13 + justify-content: center;
     14 + user-select: none;
     15 + margin-left: -7px;
     16 + 
     17 + @include xxl {
     18 + width: 44px;
     19 + height: 44px;
     20 + }
     21 + 
     22 + &:not(:first-child) {
     23 + margin-left: -7px;
     24 + }
     25 + 
     26 + &:last-child {
     27 + margin-left: 0;
     28 + }
     29 + 
     30 + @include mobile-and-tablet {
     31 + width: 36px;
     32 + height: 36px;
     33 + }
     34 +}
     35 + 
     36 +.avatarImage {
     37 + width: 100%;
     38 + height: 100%;
     39 + object-fit: cover;
     40 +}
     41 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Avatar/Avatar.tsx
     1 +import React from 'react';
     2 +import styles from './Avatar.module.scss';
     3 + 
     4 +type Props = {
     5 + src: string;
     6 + alt?: string;
     7 +};
     8 + 
     9 +const Avatar = ({ src, alt = '' }: Props) => (
     10 + <div className={styles.avatarItem}>
     11 + <img src={src} className={styles.avatarImage} alt={alt} />
     12 + </div>
     13 +);
     14 + 
     15 +export default Avatar;
     16 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/AvatarGroup/AvatarGroup.module.scss
    1  -@import '~styles/_vars.scss';
    2  -@import '~styles/responsive.scss';
    3  - 
    4  -.avatarsWrapper {
    5  - flex: 1;
    6  - display: flex;
    7  - flex-direction: column;
    8  - justify-content: flex-end;
    9  -}
    10  - 
    11 1  .avatars {
    12  - display: flex;
    13  - align-items: center;
    14  -}
    15  - 
    16  -.avatarGroup {
    17  - padding-left: 0;
    18  - margin-top: 0;
    19  - margin-bottom: 0;
    20  - list-style: none;
    21 2   flex-shrink: 0;
    22 3   display: flex;
    23 4   align-items: center;
    24 5   flex-direction: row-reverse;
    25 6  }
    26 7   
    27  -.avatarItem {
    28  - width: 32px;
    29  - height: 32px;
    30  - border: 2px solid $white;
    31  - border-radius: 50%;
    32  - overflow: hidden;
    33  - flex-shrink: 0;
    34  - display: flex;
    35  - align-items: center;
    36  - justify-content: center;
    37  - user-select: none;
    38  - margin-left: -7px;
    39  - 
    40  - @include xxl {
    41  - width: 40px;
    42  - height: 40px;
    43  - }
    44  - 
    45  - &:not(:first-child) {
    46  - margin-left: -7px;
    47  - }
    48  - 
    49  - &:last-child {
    50  - margin-left: 0;
    51  - }
    52  - 
    53  - @include mobile-and-tablet {
    54  - width: 36px;
    55  - height: 36px;
    56  - }
    57  -}
    58  - 
    59  -.avatarImage {
    60  - width: 100%;
    61  - height: 100%;
    62  - object-fit: cover;
    63  -}
    64  - 
    65  -.counter {
    66  - white-space: nowrap;
    67  - font-size: 16px;
    68  - line-height: 30px;
    69  - margin-left: 16px;
    70  - 
    71  - @include xxl {
    72  - font-size: 19px;
    73  - }
    74  - 
    75  - @include mobile-and-tablet {
    76  - font-size: 16px;
    77  - line-height: 1.62;
    78  - }
    79  -}
    80  - 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/AvatarGroup/AvatarGroup.stories.tsx
    1 1  import React from 'react';
    2 2  import { ComponentStory, ComponentMeta } from '@storybook/react';
    3 3  import AvatarGroup from './AvatarGroup';
     4 +import Avatar from '../Avatar/Avatar';
    4 5   
    5 6  export default {
    6 7   title: 'Interface / AvatarGroup',
    skipped 3 lines
    10 11   },
    11 12  } as ComponentMeta<typeof AvatarGroup>;
    12 13   
    13  -const Template: ComponentStory<typeof AvatarGroup> = (args) => <AvatarGroup {...args} />;
    14  - 
    15  -export const Default = Template.bind({});
    16  -Default.args = {
    17  - avatarGroup: [
    18  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    19  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    20  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    21  - ],
    22  - counter: 5265,
    23  -};
     14 +export const Primary: ComponentStory<typeof AvatarGroup> = () => (
     15 + <div style={{ display: 'flex', justifyContent: 'center' }}>
     16 + <AvatarGroup>
     17 + <Avatar src='https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg' />
     18 + <Avatar src='https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg' />
     19 + <Avatar src='https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg' />
     20 + </AvatarGroup>
     21 + </div>
     22 +);
    24 23   
  • ■ ■ ■ ■ ■
    packages/web/src/components/ui/AvatarGroup/AvatarGroup.tsx
    1 1  import React from 'react';
    2 2  import styles from './AvatarGroup.module.scss';
    3  -import { formatNumber } from '../../../utils/helpers';
    4 3   
    5 4  type Props = {
    6  - avatarGroup: string[];
    7  - counter: number;
     5 + children: React.ReactNode;
    8 6  };
    9 7   
    10  -export default function AvatarGroup({ avatarGroup, counter }: Props) {
    11  - return (
    12  - <div className={styles.avatarsWrapper}>
    13  - <div className={styles.avatars}>
    14  - <div className={styles.avatarGroup}>
    15  - {avatarGroup.map((avatar, idx) => (
    16  - <div key={idx} className={styles.avatarItem}>
    17  - <img src={avatar} className={styles.avatarImage} alt='' />
    18  - </div>
    19  - ))}
    20  - </div>
     8 +const AvatarGroup = ({ children }: Props) => <div className={styles.avatars}>{children}</div>;
    21 9   
    22  - <div className={styles.counter}>+ {formatNumber(counter)} sites use</div>
    23  - </div>
    24  - </div>
    25  - );
    26  -}
     10 +export default AvatarGroup;
    27 11   
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/BarChart/BarChart.module.scss
     1 +@import '~styles/_vars.scss';
     2 +@import '~styles/responsive.scss';
     3 + 
     4 +.popularity {
     5 + margin-top: 16px;
     6 + display: flex;
     7 + overflow-x: auto;
     8 + 
     9 + &::-webkit-scrollbar {
     10 + display: none;
     11 + }
     12 + 
     13 + @include mobile {
     14 + margin: 0 -20px;
     15 + padding: 0 20px;
     16 + display: flex;
     17 + flex-wrap: nowrap;
     18 + }
     19 +}
     20 + 
     21 +.popularityItemWrapper {
     22 + min-width: 100px;
     23 + flex-shrink: 0;
     24 + margin-right: 10px;
     25 + 
     26 + &:last-child {
     27 + margin-right: 0;
     28 + }
     29 + 
     30 + @include mobile-and-tablet {
     31 + min-width: 75px;
     32 + }
     33 +}
     34 + 
     35 +.popularityItem {
     36 + height: 282px;
     37 + background: $gray-surface;
     38 + border-radius: 12px;
     39 + display: flex;
     40 + flex-direction: column;
     41 + justify-content: flex-end;
     42 +}
     43 + 
     44 +.popularityFill {
     45 + border-radius: 10px;
     46 + background: $gray-border;
     47 + display: flex;
     48 + flex-direction: column;
     49 + justify-content: flex-end;
     50 + align-items: center;
     51 + font-weight: 500;
     52 + color: $gray-text;
     53 + padding-bottom: 14px;
     54 +}
     55 + 
     56 +.popularityFillSkeleton {
     57 + padding-bottom: 0;
     58 + height: 100%;
     59 +}
     60 + 
     61 +.popularitySkeleton {
     62 + padding-bottom: 0;
     63 + transform: none;
     64 +}
     65 + 
     66 +.popularityFillAccent {
     67 + background: $blue-accent;
     68 + color: #ffffff;
     69 +}
     70 + 
     71 +.popularityVersion {
     72 + display: flex;
     73 + align-items: center;
     74 + justify-content: center;
     75 + margin-top: 16px;
     76 + font-family: $font-monospace;
     77 + font-weight: 500;
     78 +}
     79 + 
     80 +.popularityVersionSkeleton {
     81 + margin: 15px auto 0;
     82 +}
     83 + 
     84 +.popularityVersionIcon {
     85 + margin-left: 8px;
     86 +}
     87 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/BarChart/BarChart.tsx
     1 +import React, { useMemo } from 'react';
     2 +import styles from './BarChart.module.scss';
     3 +import clsx from 'clsx';
     4 +import { formatNumber } from '../../../utils/helpers';
     5 +import { Icon } from '../Icon/Icon';
     6 + 
     7 +type Bar = {
     8 + fill: number; // expects a floating point number between 0 and 1
     9 + uses: number;
     10 + moduleVersion: string;
     11 + vulnerabilities?: boolean;
     12 + highlighted?: boolean;
     13 +};
     14 + 
     15 +type Props = {
     16 + bars: Bar[];
     17 +};
     18 + 
     19 +const Bar = ({ fill, uses, moduleVersion, vulnerabilities, highlighted }: Bar) => {
     20 + const fillHeightStyle = useMemo(() => {
     21 + const normalizedFill = Math.max(Math.min(fill, 1), 0);
     22 + 
     23 + return {
     24 + height: `${normalizedFill * 100}%`,
     25 + };
     26 + }, [fill]);
     27 + 
     28 + return (
     29 + <div className={styles.popularityItemWrapper}>
     30 + <div className={styles.popularityItem}>
     31 + <div
     32 + className={clsx(styles.popularityFill, highlighted && styles.popularityFillAccent)}
     33 + style={fillHeightStyle}
     34 + >
     35 + {formatNumber(uses)}
     36 + </div>
     37 + </div>
     38 + 
     39 + <div className={styles.popularityVersion}>
     40 + {moduleVersion}
     41 + {vulnerabilities && (
     42 + <Icon kind='bugOutlined' color='#212121' className={styles.popularityVersionIcon} />
     43 + )}
     44 + </div>
     45 + </div>
     46 + );
     47 +};
     48 + 
     49 +export default function BarChart({ bars }: Props) {
     50 + return (
     51 + <div className={styles.popularity}>
     52 + {bars.map((bar) => (
     53 + <Bar key={bar.uses} {...bar} />
     54 + ))}
     55 + </div>
     56 + );
     57 +}
     58 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/BarChart/BarChartSkeleton.tsx
     1 +import React from 'react';
     2 +import styles from './BarChart.module.scss';
     3 +import clsx from 'clsx';
     4 +import { repeat } from 'utils/helpers';
     5 +import Skeleton from '../Skeleton/Skeleton';
     6 + 
     7 +const PopularitySkeleton = () => (
     8 + <Skeleton
     9 + variant='rectangular'
     10 + width='100%'
     11 + height='100%'
     12 + className={styles.popularitySkeleton}
     13 + />
     14 +);
     15 + 
     16 +const PopularityVersionSkeleton = () => (
     17 + <Skeleton width={64} className={styles.popularityVersionSkeleton} />
     18 +);
     19 + 
     20 +export default function BarChartSkeleton() {
     21 + return (
     22 + <div className={styles.popularity}>
     23 + {repeat(
     24 + 6,
     25 + <div className={styles.popularityItemWrapper}>
     26 + <div className={styles.popularityItem}>
     27 + <div className={clsx(styles.popularityFill, styles.popularityFillSkeleton)}>
     28 + <PopularitySkeleton />
     29 + </div>
     30 + </div>
     31 + 
     32 + <PopularityVersionSkeleton />
     33 + </div>
     34 + )}
     35 + </div>
     36 + );
     37 +}
     38 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Card/Card.module.scss
    skipped 78 lines
    79 79   }
    80 80  }
    81 81   
    82  -.toAll {
     82 +.placeholder {
     83 + cursor: pointer;
     84 + color: $black;
    83 85   background-color: $gray-surface;
    84 86  }
    85 87   
    skipped 15 lines
    101 103   padding: 0 0 0 2px;
    102 104  }
    103 105   
    104  -.vulnerable {
     106 +.small {
    105 107   min-height: 136px;
    106 108   justify-content: space-between;
    107 109   max-width: initial;
    skipped 3 lines
    111 113   }
    112 114  }
    113 115   
     116 +.avatarsWrapper {
     117 + flex: 1;
     118 + align-self: flex-start;
     119 + display: flex;
     120 + align-items: flex-end;
     121 +}
     122 + 
     123 +.avatarSites {
     124 + display: flex;
     125 + align-items: center;
     126 +}
     127 + 
     128 +.counter {
     129 + white-space: nowrap;
     130 + font-size: 16px;
     131 + line-height: 30px;
     132 + margin-left: 14px;
     133 + 
     134 + @include xxl {
     135 + font-size: 19px;
     136 + }
     137 + 
     138 + @include mobile-and-tablet {
     139 + font-size: 16px;
     140 + line-height: 1.62;
     141 + }
     142 +}
     143 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Card/Card.stories.tsx
    1  -import React from 'react';
    2  -import { ComponentMeta, ComponentStory } from '@storybook/react';
    3  -import Card from './Card';
    4  - 
    5  -export default {
    6  - title: 'Interface / Card',
    7  - component: Card,
    8  - parameters: {
    9  - layout: 'centered',
    10  - },
    11  -} as ComponentMeta<typeof Card>;
    12  - 
    13  -const Template: ComponentStory<typeof Card> = (args) => <Card {...args} />;
    14  - 
    15  -export const PopularSearchQueries = Template.bind({});
    16  -PopularSearchQueries.args = {
    17  - id: 'uExBVGuF',
    18  - title: 'github.com',
    19  - icon: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    20  - packageTags: {
    21  - featuredPackages: ['mdast-util-from-markdown', 'react', 'react-dom'],
    22  - restPackages: 45,
    23  - },
    24  -};
    25  - 
    26  -export const PopularPackages = Template.bind({});
    27  -PopularPackages.args = {
    28  - id: '1EkL1u5g',
    29  - title: '@team-griffin/react-heading-section',
    30  - description:
    31  - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
    32  - featuredSites: {
    33  - iconList: [
    34  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    35  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    36  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    37  - ],
    38  - numberOfUses: 5265,
    39  - },
    40  -};
    41  - 
    42  -export const VulnerableSites = Template.bind({});
    43  -VulnerableSites.args = {
    44  - id: 'mhwO2bPM',
    45  - title: 'disneyland.omsk.ru/signup',
    46  - vulnerablePackage: {
    47  - name: 'mdast-util-from-markdown',
    48  - moreCount: 1,
    49  - },
    50  - variant: 'vulnerable',
    51  -};
    52  - 
  • ■ ■ ■ ■ ■
    packages/web/src/components/ui/Card/Card.tsx
    1 1  import React from 'react';
    2 2  import styles from './Card.module.scss';
    3 3  import clsx from 'clsx';
    4  -import Chip from '../Chip/Chip';
    5  -import ChipGroup from '../ChipGroup/ChipGroup';
    6  -import AvatarGroup from '../AvatarGroup/AvatarGroup';
    7  -import VulnerablePackage from '../VulnerablePackage/VulnerablePackage';
    8  -import { Icon } from '../Icon/Icon';
    9 4   
    10  -export type CardProps = {
    11  - id: string;
    12  - title: string;
     5 +export type CardCommonProps = {
     6 + id?: string;
     7 + to?: string;
     8 + title?: string;
    13 9   icon?: string;
    14 10   description?: string;
    15  - packageTags?: {
    16  - featuredPackages: string[];
    17  - restPackages: number;
    18  - };
    19  - featuredSites?: {
    20  - iconList: string[];
    21  - numberOfUses: number;
    22  - };
    23  - vulnerablePackage?: {
    24  - name: string;
    25  - moreCount?: number;
    26  - };
    27  - variant?: 'toAll' | 'vulnerable';
     11 + variant?: 'placeholder' | 'small';
     12 + children?: React.ReactNode;
    28 13  };
    29 14   
    30  -export default function Card({
    31  - title,
    32  - icon,
    33  - description,
    34  - packageTags,
    35  - featuredSites,
    36  - vulnerablePackage,
    37  - variant,
    38  -}: CardProps) {
     15 +const Card = ({ to, title, variant, icon, description, children }: CardCommonProps) => {
     16 + const Tag = to ? 'a' : 'div';
     17 + 
    39 18   return (
    40  - <div className={clsx(styles.card, variant && styles[variant])}>
     19 + <Tag href={to ?? undefined} className={clsx(styles.card, variant && styles[variant])}>
    41 20   <div className={styles.cardTop}>
    42 21   <div className={styles.header}>
    43 22   {icon && (
    skipped 8 lines
    52 31   {description && <div className={styles.description}>{description}</div>}
    53 32   </div>
    54 33   
    55  - {variant === 'toAll' && (
    56  - <button className={styles.arrowBtn} type='button'>
    57  - <Icon kind='arrow' color='#212121' width={10} height={18} />
    58  - </button>
    59  - )}
     34 + {children}
     35 + </Tag>
     36 + );
     37 +};
    60 38   
    61  - {packageTags && (
    62  - <div className={styles.tagsWrapper}>
    63  - <ChipGroup chips={packageTags.featuredPackages} size='large'>
    64  - <Chip className={styles.tag} variant='outlined' size='large'>
    65  - +{packageTags.restPackages} packages
    66  - </Chip>
    67  - </ChipGroup>
    68  - </div>
    69  - )}
    70  - 
    71  - {featuredSites && (
    72  - <AvatarGroup avatarGroup={featuredSites.iconList} counter={featuredSites.numberOfUses} />
    73  - )}
    74  - 
    75  - {vulnerablePackage && (
    76  - <VulnerablePackage name={vulnerablePackage.name} moreTotal={vulnerablePackage.moreCount} />
    77  - )}
    78  - </div>
    79  - );
    80  -}
     39 +export default Card;
    81 40   
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Card/NavigationCard.stories.tsx
     1 +import React from 'react';
     2 +import { ComponentMeta, ComponentStory } from '@storybook/react';
     3 +import NavigationCard from './NavigationCard';
     4 + 
     5 +export default {
     6 + title: 'Interface / Card',
     7 + component: NavigationCard,
     8 +} as ComponentMeta<typeof NavigationCard>;
     9 + 
     10 +const Template: ComponentStory<typeof NavigationCard> = (args) => <NavigationCard {...args} />;
     11 + 
     12 +export const Navigation = Template.bind({});
     13 +Navigation.args = {
     14 + title: 'Go to all Popular packages',
     15 + to: '/',
     16 +};
     17 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Card/NavigationCard.tsx
     1 +import React from 'react';
     2 +import Card, { CardCommonProps } from './Card';
     3 +import styles from './Card.module.scss';
     4 +import { Icon } from '../Icon/Icon';
     5 + 
     6 +const NavigationCard = ({ title, to }: CardCommonProps) => (
     7 + <Card to={to} title={title} variant='placeholder'>
     8 + <button className={styles.arrowBtn}>
     9 + <Icon kind='arrow' color='#212121' width={10} height={18} />
     10 + </button>
     11 + </Card>
     12 +);
     13 + 
     14 +export default NavigationCard;
     15 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Card/PackagesBySourceCard.stories.tsx
     1 +import React from 'react';
     2 +import { ComponentMeta, ComponentStory } from '@storybook/react';
     3 +import PackagesBySourceCard from './PackagesBySourceCard';
     4 + 
     5 +export default {
     6 + title: 'Interface / Card',
     7 + component: PackagesBySourceCard,
     8 +} as ComponentMeta<typeof PackagesBySourceCard>;
     9 + 
     10 +const Template: ComponentStory<typeof PackagesBySourceCard> = (args) => (
     11 + <PackagesBySourceCard {...args} />
     12 +);
     13 + 
     14 +export const PopularSearchQueries = Template.bind({});
     15 +PopularSearchQueries.args = {
     16 + sourceTitle: 'github.com',
     17 + sourceIcon: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     18 + packages: ['mdast-util-from-markdown', 'react', 'react-dom'],
     19 + morePackagesCount: 45,
     20 +};
     21 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Card/PackagesBySourceCard.tsx
     1 +import React from 'react';
     2 +import styles from './Card.module.scss';
     3 +import Card from './Card';
     4 +import Chip from '../Chip/Chip';
     5 +import ChipGroup from '../ChipGroup/ChipGroup';
     6 + 
     7 +export type PackagesBySourceCardProps = {
     8 + sourceTitle: string;
     9 + sourceIcon: string;
     10 + packages: string[];
     11 + morePackagesCount?: number;
     12 +};
     13 + 
     14 +const PackagesBySourceCard = ({
     15 + sourceTitle,
     16 + sourceIcon,
     17 + packages,
     18 + morePackagesCount,
     19 +}: PackagesBySourceCardProps) => (
     20 + <Card title={sourceTitle} icon={sourceIcon}>
     21 + <div className={styles.tagsWrapper}>
     22 + <ChipGroup>
     23 + {packages.map((chip) => (
     24 + <Chip key={chip} className={styles.chip} size='large' font='monospace'>
     25 + {chip}
     26 + </Chip>
     27 + ))}
     28 + 
     29 + {morePackagesCount && (
     30 + <Chip className={styles.chip} size='large' variant='outlined'>
     31 + +{morePackagesCount} packages
     32 + </Chip>
     33 + )}
     34 + </ChipGroup>
     35 + </div>
     36 + </Card>
     37 +);
     38 + 
     39 +export default PackagesBySourceCard;
     40 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Card/PopularPackageCard.stories.tsx
     1 +import React from 'react';
     2 +import { ComponentMeta, ComponentStory } from '@storybook/react';
     3 +import PopularPackageCard from './PopularPackageCard';
     4 + 
     5 +export default {
     6 + title: 'Interface / Card',
     7 + component: PopularPackageCard,
     8 +} as ComponentMeta<typeof PopularPackageCard>;
     9 + 
     10 +const Template: ComponentStory<typeof PopularPackageCard> = (args) => (
     11 + <PopularPackageCard {...args} />
     12 +);
     13 + 
     14 +export const PopularPackages = Template.bind({});
     15 +PopularPackages.args = {
     16 + packageName: '@team-griffin/react-heading-section',
     17 + packageDescription:
     18 + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
     19 + hostsFaviconList: [
     20 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     21 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     22 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     23 + ],
     24 + totalUsageCount: 5265,
     25 +};
     26 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Card/PopularPackageCard.tsx
     1 +import React from 'react';
     2 +import styles from './Card.module.scss';
     3 +import Card from './Card';
     4 +import AvatarGroup from '../AvatarGroup/AvatarGroup';
     5 +import Avatar from '../Avatar/Avatar';
     6 +import { formatNumber } from '../../../utils/helpers';
     7 + 
     8 +export type PopularPackageCardProps = {
     9 + packageName: string;
     10 + packageDescription?: string;
     11 + hostsFaviconList: string[];
     12 + totalUsageCount: number;
     13 +};
     14 + 
     15 +const PopularPackageCard = ({
     16 + packageName,
     17 + packageDescription,
     18 + hostsFaviconList,
     19 + totalUsageCount,
     20 +}: PopularPackageCardProps) => (
     21 + <Card title={packageName} description={packageDescription}>
     22 + <div className={styles.avatarsWrapper}>
     23 + <div className={styles.avatarSites}>
     24 + <AvatarGroup>
     25 + {hostsFaviconList.map((icon) => (
     26 + <Avatar src={icon} />
     27 + ))}
     28 + </AvatarGroup>
     29 + 
     30 + <div className={styles.counter}>+ {formatNumber(totalUsageCount)} sites use</div>
     31 + </div>
     32 + </div>
     33 + </Card>
     34 +);
     35 + 
     36 +export default PopularPackageCard;
     37 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Card/ScansWithVulnerabilitiesCard.stories.tsx
     1 +import React from 'react';
     2 +import { ComponentMeta, ComponentStory } from '@storybook/react';
     3 +import ScansWithVulnerabilitiesCard from './ScansWithVulnerabilitiesCard';
     4 + 
     5 +export default {
     6 + title: 'Interface / Card',
     7 + component: ScansWithVulnerabilitiesCard,
     8 +} as ComponentMeta<typeof ScansWithVulnerabilitiesCard>;
     9 + 
     10 +const Template: ComponentStory<typeof ScansWithVulnerabilitiesCard> = (args) => (
     11 + <ScansWithVulnerabilitiesCard {...args} />
     12 +);
     13 + 
     14 +export const VulnerableSites = Template.bind({});
     15 +VulnerableSites.args = {
     16 + sourcePageUrl: 'disneyland.omsk.ru/signup',
     17 + vulnerablePackageName: 'mdast-util-from-markdown',
     18 + additionalVulnerabilitiesCount: 1,
     19 +};
     20 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Card/ScansWithVulnerabilitiesCard.tsx
     1 +import React from 'react';
     2 +import Card from './Card';
     3 +import VulnerablePackage from '../VulnerablePackage/VulnerablePackage';
     4 + 
     5 +export type ScansWithVulnerabilitiesCardProps = {
     6 + sourcePageUrl: string;
     7 + vulnerablePackageName: string;
     8 + additionalVulnerabilitiesCount?: number;
     9 +};
     10 + 
     11 +const ScansWithVulnerabilitiesCard = ({
     12 + sourcePageUrl,
     13 + vulnerablePackageName,
     14 + additionalVulnerabilitiesCount,
     15 +}: ScansWithVulnerabilitiesCardProps) => (
     16 + <Card title={sourcePageUrl} variant='small'>
     17 + <VulnerablePackage name={vulnerablePackageName} moreTotal={additionalVulnerabilitiesCount} />
     18 + </Card>
     19 +);
     20 + 
     21 +export default ScansWithVulnerabilitiesCard;
     22 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/CardList/CardList.stories.tsx
    1  -import React from 'react';
    2  -import { ComponentMeta, ComponentStory } from '@storybook/react';
    3  -import Container from '../Container/Container';
    4  -import CardList from './CardList';
    5  - 
    6  -export default {
    7  - title: 'Interface / Card List',
    8  - component: CardList,
    9  - parameters: {
    10  - layout: 'fullscreen',
    11  - },
    12  -} as ComponentMeta<typeof CardList>;
    13  - 
    14  -const Template: ComponentStory<typeof CardList> = (args) => (
    15  - <Container>
    16  - <CardList {...args} />
    17  - </Container>
    18  -);
    19  - 
    20  -export const PopularSearchQueries = Template.bind({});
    21  -PopularSearchQueries.args = {
    22  - cards: [
    23  - {
    24  - id: 'uExBVGuF',
    25  - title: 'github.com',
    26  - icon: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    27  - packageTags: {
    28  - featuredPackages: ['mdast-util-from-markdown', 'react', 'react-dom'],
    29  - restPackages: 45,
    30  - },
    31  - },
    32  - {
    33  - id: '1EkL1u5g',
    34  - title: 'fingerprint.com',
    35  - icon: 'https://avatars.githubusercontent.com/u/67208791?s=200&v=4',
    36  - packageTags: {
    37  - featuredPackages: ['mdast-util-from-markdown', 'react', 'react-dom'],
    38  - restPackages: 45,
    39  - },
    40  - },
    41  - {
    42  - id: 'mhwO2bPM',
    43  - title: 'facebook.com',
    44  - icon: 'https://avatars.githubusercontent.com/u/69631?s=200&v=4',
    45  - packageTags: {
    46  - featuredPackages: ['react'],
    47  - restPackages: 45,
    48  - },
    49  - },
    50  - ],
    51  -};
    52  - 
    53  -export const PopularPackages = Template.bind({});
    54  -PopularPackages.args = {
    55  - cards: [
    56  - {
    57  - id: 'FPsBcl8R',
    58  - title: '@team-griffin/react-heading-section',
    59  - description: "This package's job is to automatically determine...",
    60  - featuredSites: {
    61  - iconList: [
    62  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    63  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    64  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    65  - ],
    66  - numberOfUses: 5265,
    67  - },
    68  - },
    69  - {
    70  - id: 'emtYcsUh',
    71  - title: 'unist-util-generated',
    72  - description: 'unist utility to check if a node is generated',
    73  - featuredSites: {
    74  - iconList: [
    75  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    76  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    77  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    78  - ],
    79  - numberOfUses: 5265,
    80  - },
    81  - },
    82  - {
    83  - id: 'TYIwvAfy',
    84  - title: 'react-smooth',
    85  - description: 'is a animation library work on React',
    86  - featuredSites: {
    87  - iconList: [
    88  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    89  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    90  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    91  - ],
    92  - numberOfUses: 5265,
    93  - },
    94  - },
    95  - {
    96  - id: 'Lq1pEEX7',
    97  - title: 'unist-util-position',
    98  - description: 'unist utility to get the positional info of nodes',
    99  - featuredSites: {
    100  - iconList: [
    101  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    102  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    103  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    104  - ],
    105  - numberOfUses: 5265,
    106  - },
    107  - },
    108  - {
    109  - id: 'cWOgIbmp',
    110  - title: 'vfile-message',
    111  - description: 'Create vfile messages',
    112  - featuredSites: {
    113  - iconList: [
    114  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    115  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    116  - 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    117  - ],
    118  - numberOfUses: 5265,
    119  - },
    120  - },
    121  - {
    122  - id: 'UT97Vpoi',
    123  - title: 'Go to all Popular packages',
    124  - variant: 'toAll',
    125  - },
    126  - ],
    127  -};
    128  - 
    129  -export const VulnerableSites = Template.bind({});
    130  -VulnerableSites.args = {
    131  - variant: 'vertical',
    132  - cards: [
    133  - {
    134  - id: 'LnO9Xynn',
    135  - title: 'disneyland.omsk.ru/signup',
    136  - vulnerablePackage: {
    137  - name: 'mdast-util-from-markdown',
    138  - },
    139  - variant: 'vulnerable',
    140  - },
    141  - {
    142  - id: '-A74UAy8',
    143  - title: 'disneyland.omsk.ru/signup',
    144  - vulnerablePackage: {
    145  - name: 'mdast-util-from-markdown',
    146  - moreCount: 1,
    147  - },
    148  - variant: 'vulnerable',
    149  - },
    150  - {
    151  - id: 'DPa05I2W',
    152  - title: 'disneyland.omsk.ru/signup',
    153  - vulnerablePackage: {
    154  - name: 'mdast-util-from-markdown',
    155  - },
    156  - variant: 'vulnerable',
    157  - },
    158  - ],
    159  -};
    160  - 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/CardList/CardList.tsx
    1 1  import React from 'react';
    2 2  import styles from './CardList.module.scss';
    3  -import Card, { CardProps } from '../Card/Card';
    4 3  import clsx from 'clsx';
    5 4   
    6 5  type Props = {
    7  - cards: CardProps[];
    8  - variant?: 'default' | 'vertical';
     6 + variant?: 'vertical';
     7 + children: React.ReactNode;
    9 8  };
    10 9   
    11  -export default function CardList({ cards, variant = 'default' }: Props) {
    12  - return (
    13  - <div className={clsx(styles.grid, styles[variant])}>
    14  - {cards.map((card) => (
    15  - <Card key={card.id} {...card} />
    16  - ))}
    17  - </div>
    18  - );
    19  -}
     10 +const CardList = ({ variant, children }: Props) => (
     11 + <div className={clsx(styles.grid, variant)}>{children}</div>
     12 +);
     13 + 
     14 +export default CardList;
    20 15   
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/CardList/CardListSkeleton.tsx
    skipped 3 lines
    4 4  import Skeleton from '../Skeleton/Skeleton';
    5 5  import Card from '../Card/Card';
    6 6   
    7  -export const CardListSkeleton = () => (
     7 +type Props = {
     8 + numberOfElements?: number;
     9 +};
     10 + 
     11 +export const CardListSkeleton = ({ numberOfElements = 3 }: Props) => (
    8 12   <div className={styles.grid}>
    9 13   {repeat(
    10  - 3,
     14 + numberOfElements,
    11 15   <Skeleton width='100%' variant='rounded'>
    12  - <Card id='id1' title='title' />
     16 + <Card />
    13 17   </Skeleton>
    14 18   )}
    15 19   </div>
    skipped 2 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/CardList/PackagesBySourceCardList.stories.tsx
     1 +import React from 'react';
     2 +import { ComponentMeta, ComponentStory } from '@storybook/react';
     3 +import Container from '../Container/Container';
     4 +import PackagesBySourceCardList from './PackagesBySourceCardList';
     5 +import { packagesBySourceListData } from '../../../mocks/CardListsMocks';
     6 + 
     7 +export default {
     8 + title: 'Interface / Card List',
     9 + component: PackagesBySourceCardList,
     10 + parameters: {
     11 + layout: 'fullscreen',
     12 + },
     13 +} as ComponentMeta<typeof PackagesBySourceCardList>;
     14 + 
     15 +const Template: ComponentStory<typeof PackagesBySourceCardList> = (args) => (
     16 + <Container>
     17 + <PackagesBySourceCardList {...args} />
     18 + </Container>
     19 +);
     20 + 
     21 +export const PopularSearchQueries = Template.bind({});
     22 +PopularSearchQueries.args = {
     23 + cards: packagesBySourceListData,
     24 +};
     25 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/CardList/PackagesBySourceCardList.tsx
     1 +import React from 'react';
     2 +import CardList from './CardList';
     3 +import PackagesBySourceCard, { PackagesBySourceCardProps } from '../Card/PackagesBySourceCard';
     4 + 
     5 +export type KeyedPackagesBySourceCardProps = PackagesBySourceCardProps & {
     6 + id: string;
     7 +};
     8 + 
     9 +type Props = {
     10 + cards: KeyedPackagesBySourceCardProps[];
     11 +};
     12 + 
     13 +const PackagesBySourceCardList = ({ cards }: Props) => (
     14 + <CardList>
     15 + {cards.map((card) => (
     16 + <PackagesBySourceCard key={card.id} {...card} />
     17 + ))}
     18 + </CardList>
     19 +);
     20 + 
     21 +export default PackagesBySourceCardList;
     22 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/CardList/PopularPackageCardList.stories.tsx
     1 +import React from 'react';
     2 +import { ComponentMeta, ComponentStory } from '@storybook/react';
     3 +import Container from '../Container/Container';
     4 +import PopularPackageCardList from './PopularPackageCardList';
     5 +import { popularPackageListData } from '../../../mocks/CardListsMocks';
     6 + 
     7 +export default {
     8 + title: 'Interface / Card List',
     9 + component: PopularPackageCardList,
     10 + parameters: {
     11 + layout: 'fullscreen',
     12 + },
     13 +} as ComponentMeta<typeof PopularPackageCardList>;
     14 + 
     15 +const Template: ComponentStory<typeof PopularPackageCardList> = (args) => (
     16 + <Container>
     17 + <PopularPackageCardList {...args} />
     18 + </Container>
     19 +);
     20 + 
     21 +export const PopularPackages = Template.bind({});
     22 +PopularPackages.args = {
     23 + cards: popularPackageListData,
     24 +};
     25 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/CardList/PopularPackageCardList.tsx
     1 +import React from 'react';
     2 +import NavigationCard from '../Card/NavigationCard';
     3 +import PopularPackageCard, { PopularPackageCardProps } from '../Card/PopularPackageCard';
     4 +import CardList from './CardList';
     5 + 
     6 +export type KeyedPopularPackageCardProps = PopularPackageCardProps & {
     7 + id: string;
     8 +};
     9 + 
     10 +type Props = {
     11 + cards: KeyedPopularPackageCardProps[];
     12 +};
     13 + 
     14 +const PopularPackageCardList = ({ cards }: Props) => {
     15 + return (
     16 + <CardList>
     17 + {cards.map((card) => (
     18 + <PopularPackageCard key={card.id} {...card} />
     19 + ))}
     20 + <NavigationCard title='Go to all Popular packages' to='/' />
     21 + </CardList>
     22 + );
     23 +};
     24 + 
     25 +export default PopularPackageCardList;
     26 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/CardList/ScansWithVulnerabilitiesCardList.stories.tsx
     1 +import React from 'react';
     2 +import { ComponentMeta, ComponentStory } from '@storybook/react';
     3 +import Container from '../Container/Container';
     4 +import ScansWithVulnerabilitiesCardList from './ScansWithVulnerabilitiesCardList';
     5 +import { scansWithVulnerabilitiesListData } from '../../../mocks/CardListsMocks';
     6 + 
     7 +export default {
     8 + title: 'Interface / Card List',
     9 + component: ScansWithVulnerabilitiesCardList,
     10 + parameters: {
     11 + layout: 'fullscreen',
     12 + },
     13 +} as ComponentMeta<typeof ScansWithVulnerabilitiesCardList>;
     14 + 
     15 +const Template: ComponentStory<typeof ScansWithVulnerabilitiesCardList> = (args) => (
     16 + <Container>
     17 + <ScansWithVulnerabilitiesCardList {...args} />
     18 + </Container>
     19 +);
     20 + 
     21 +export const VulnerableSites = Template.bind({});
     22 +VulnerableSites.args = {
     23 + cards: scansWithVulnerabilitiesListData,
     24 +};
     25 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/CardList/ScansWithVulnerabilitiesCardList.tsx
     1 +import React from 'react';
     2 +import CardList from './CardList';
     3 +import ScansWithVulnerabilitiesCard, {
     4 + ScansWithVulnerabilitiesCardProps,
     5 +} from '../Card/ScansWithVulnerabilitiesCard';
     6 + 
     7 +export type KeyedScansWithVulnerabilitiesCardProps = ScansWithVulnerabilitiesCardProps & {
     8 + id: string;
     9 +};
     10 + 
     11 +type Props = {
     12 + cards: KeyedScansWithVulnerabilitiesCardProps[];
     13 +};
     14 + 
     15 +const ScansWithVulnerabilitiesCardList = ({ cards }: Props) => {
     16 + return (
     17 + <CardList variant='vertical'>
     18 + {cards.map((card) => (
     19 + <ScansWithVulnerabilitiesCard key={card.id} {...card} />
     20 + ))}
     21 + </CardList>
     22 + );
     23 +};
     24 + 
     25 +export default ScansWithVulnerabilitiesCardList;
     26 + 
  • ■ ■ ■ ■
    packages/web/src/components/ui/Chip/Chip.module.scss
    skipped 108 lines
    109 109   font-weight: 500;
    110 110  }
    111 111   
    112  -.vulnerability {
     112 +.vulnerabilities {
    113 113   background-color: $red-accent;
    114 114   color: $white;
    115 115  }
    skipped 21 lines
  • ■ ■ ■ ■
    packages/web/src/components/ui/Chip/Chip.tsx
    skipped 10 lines
    11 11   | 'secondary'
    12 12   | 'outlined'
    13 13   | 'suggest'
    14  - | 'vulnerability'
     14 + | 'vulnerabilities'
    15 15   | 'duplicate'
    16 16   | 'outdated'
    17 17   | 'info';
    skipped 38 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/ChipGroup/ChipGroup.module.scss
    1  -@import '~styles/responsive.scss';
    2  - 
    3 1  .chipsWrapper {
    4 2   flex: 1;
    5 3   display: flex;
    skipped 5 lines
    11 9   flex-wrap: wrap;
    12 10   align-items: flex-start;
    13 11   justify-content: flex-start;
    14  -}
    15 12   
    16  -.chip {
    17  - margin-bottom: 8px;
     13 + & > * {
     14 + margin-bottom: 8px;
     15 + margin-right: 8px;
    18 16   
    19  - &:not(:last-child) {
    20  - margin-right: 8px;
     17 + &:last-child {
     18 + margin-right: 0;
     19 + }
    21 20   }
    22 21  }
    23 22   
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/ChipGroup/ChipGroup.stories.tsx
    1 1  import React from 'react';
    2 2  import { ComponentStory, ComponentMeta } from '@storybook/react';
    3 3  import ChipGroup from './ChipGroup';
    4  -import styles from '../Card/Card.module.scss';
    5 4  import Chip from '../Chip/Chip';
    6 5   
    7 6  export default {
    skipped 4 lines
    12 11   },
    13 12  } as ComponentMeta<typeof ChipGroup>;
    14 13   
    15  -const Template: ComponentStory<typeof ChipGroup> = (args) => <ChipGroup {...args} />;
     14 +export const Default: ComponentStory<typeof ChipGroup> = () => (
     15 + <ChipGroup>
     16 + {['mdast-util-from-markdown', 'react', 'react-dom'].map((chip) => (
     17 + <Chip key={chip}>{chip}</Chip>
     18 + ))}
     19 + </ChipGroup>
     20 +);
    16 21   
    17  -export const Default = Template.bind({});
    18  -Default.args = {
    19  - chips: ['mdast-util-from-markdown', 'react', 'react-dom'],
    20  -};
     22 +export const Medium: ComponentStory<typeof ChipGroup> = () => (
     23 + <ChipGroup>
     24 + {['mdast-util-from-markdown', 'react', 'react-dom'].map((chip) => (
     25 + <Chip key={chip} size='medium' font='monospace'>
     26 + {chip}
     27 + </Chip>
     28 + ))}
     29 + </ChipGroup>
     30 +);
    21 31   
    22  -export const WithMoreCounter = Template.bind({});
    23  -const MoreCounter = (
    24  - <Chip className={styles.tag} variant='outlined' size='large'>
    25  - +45 packages
    26  - </Chip>
     32 +export const Large: ComponentStory<typeof ChipGroup> = () => (
     33 + <ChipGroup>
     34 + {['mdast-util-from-markdown', 'react', 'react-dom'].map((chip) => (
     35 + <Chip key={chip} size='large' font='monospace'>
     36 + {chip}
     37 + </Chip>
     38 + ))}
     39 + </ChipGroup>
    27 40  );
    28  -WithMoreCounter.args = {
    29  - chips: ['mdast-util-from-markdown', 'react', 'react-dom'],
    30  - children: MoreCounter,
    31  -};
    32 41   
  • ■ ■ ■ ■ ■
    packages/web/src/components/ui/ChipGroup/ChipGroup.tsx
    1 1  import React from 'react';
    2 2  import styles from './ChipGroup.module.scss';
    3  -import Chip, { ChipProps } from '../Chip/Chip';
    4  -import { ChipGroupSkeleton } from './ChipGroupSkeleton';
    5 3   
    6 4  type Props = {
    7  - chips: string[];
    8  - children?: React.ReactNode;
    9  - size?: ChipProps['size'];
    10  - font?: ChipProps['font'];
    11  - fontSize?: ChipProps['fontSize'];
    12  - loading?: boolean;
     5 + children: React.ReactNode;
    13 6  };
    14 7   
    15  -export default function ChipGroup({
    16  - chips,
    17  - children,
    18  - size = 'medium',
    19  - font = 'monospace',
    20  - fontSize = 'regular',
    21  - loading,
    22  -}: Props) {
     8 +export default function ChipGroup({ children }: Props) {
    23 9   return (
    24 10   <div className={styles.chipsWrapper}>
    25  - <div className={styles.chips}>
    26  - {loading ? (
    27  - <ChipGroupSkeleton />
    28  - ) : (
    29  - chips.map((chip) => (
    30  - <Chip key={chip} className={styles.chip} size={size} font={font} fontSize={fontSize}>
    31  - {chip}
    32  - </Chip>
    33  - ))
    34  - )}
    35  - 
    36  - {children}
    37  - </div>
     11 + <div className={styles.chips}>{children}</div>
    38 12   </div>
    39 13   );
    40 14  }
    skipped 1 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/ChipGroup/ChipGroupSkeleton.tsx
    1 1  import React from 'react';
    2 2  import { repeat } from '../../../utils/helpers';
    3 3  import Skeleton from '../Skeleton/Skeleton';
    4  -import styles from './ChipGroup.module.scss';
     4 +import ChipGroup from './ChipGroup';
    5 5   
    6 6  export const ChipGroupSkeleton = () => (
    7  - <>{repeat(4, <Skeleton variant='rounded' width={108} height={36} className={styles.chip} />)}</>
     7 + <ChipGroup>{repeat(4, <Skeleton variant='rounded' width={108} height={36} />)}</ChipGroup>
    8 8  );
    9 9   
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Header/Header.module.scss
    skipped 1 lines
    2 2  @import '~styles/responsive.scss';
    3 3   
    4 4  .header {
     5 + transition: box-shadow $transition-duration $transition-timing-function;
     6 +}
     7 + 
     8 +.headerInner {
    5 9   display: flex;
    6 10   align-items: center;
    7 11   justify-content: space-between;
    skipped 5 lines
    13 17  }
    14 18   
    15 19  .default {
     20 + position: sticky;
     21 + top: 0;
     22 + z-index: 1;
     23 + background: $white;
    16 24   color: $black;
    17 25  }
    18 26   
    19 27  .light {
    20 28   color: $white;
     29 +}
     30 + 
     31 +.shadow {
     32 + box-shadow: inset 0px -1px 0px 0px rgba($gray-border, 0.5);
    21 33  }
    22 34   
    23 35  .showSearch {
    skipped 59 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Header/Header.tsx
    1 1  import React from 'react';
    2 2  import styles from './Header.module.scss';
    3  -import Container from '../Container/Container';
    4 3  import clsx from 'clsx';
     4 +import Container from '../Container/Container';
    5 5  import { Icon } from '../Icon/Icon';
    6 6  import SearchBar from '../SearchBar/SearchBar';
    7 7   
    8 8  export type Props = {
    9 9   variant?: 'default' | 'light';
    10 10   showSearch?: boolean;
     11 + className?: string;
    11 12   children?: React.ReactNode;
    12 13  };
    13 14   
    14  -export default function Header({ variant = 'default', showSearch = false, children }: Props) {
     15 +export default function Header({
     16 + variant = 'default',
     17 + showSearch = false,
     18 + className,
     19 + children,
     20 +}: Props) {
    15 21   return (
    16  - <Container>
    17  - <header className={clsx(styles.header, showSearch && styles.showSearch, styles[variant])}>
    18  - {/* TODO: add Link from react router */}
    19  - <a href='/' className={styles.logo}>
    20  - <Icon
    21  - kind='logo'
    22  - width={129}
    23  - height={25}
    24  - color={variant === 'light' ? 'white' : '#212121'}
    25  - />
    26  - </a>
     22 + <header className={clsx(styles.header, styles[variant], className)}>
     23 + <Container>
     24 + <div className={clsx(styles.headerInner, showSearch && styles.showSearch)}>
     25 + {/* TODO: add Link from react router */}
     26 + <a href='/' className={styles.logo}>
     27 + <Icon
     28 + kind='logo'
     29 + width={129}
     30 + height={25}
     31 + color={variant === 'light' ? 'white' : '#212121'}
     32 + />
     33 + </a>
    27 34   
    28  - {showSearch && (
    29  - <div className={styles.searchWrapper}>
    30  - <SearchBar />
    31  - </div>
    32  - )}
     35 + {showSearch && (
     36 + <div className={styles.searchWrapper}>
     37 + <SearchBar />
     38 + </div>
     39 + )}
    33 40   
    34  - <div className={styles.nav}>{children}</div>
    35  - </header>
    36  - </Container>
     41 + <div className={styles.nav}>{children}</div>
     42 + </div>
     43 + </Container>
     44 + </header>
    37 45   );
    38 46  }
    39 47   
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Header/StickyDefaultHeader.tsx
     1 +import React from 'react';
     2 +import styles from './Header.module.scss';
     3 +import { Props } from './Header';
     4 +import { useScrollPosition } from 'hooks/useScrollPosition';
     5 +import clsx from 'clsx';
     6 +import DefaultHeader from './DefaultHeader';
     7 + 
     8 +export default function StickyDefaultHeader(props: Props) {
     9 + const scrollPosition = useScrollPosition();
     10 + 
     11 + return <DefaultHeader className={clsx(scrollPosition > 0 && styles.shadow)} {...props} />;
     12 +}
     13 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Header/StickyErrorHeader.tsx
     1 +import React from 'react';
     2 +import styles from './Header.module.scss';
     3 +import { Props } from './Header';
     4 +import { useScrollPosition } from 'hooks/useScrollPosition';
     5 +import clsx from 'clsx';
     6 +import ErrorHeader from './ErrorHeader';
     7 + 
     8 +export default function StickyErrorHeader(props: Props) {
     9 + const scrollPosition = useScrollPosition();
     10 + 
     11 + return <ErrorHeader className={clsx(scrollPosition > 0 && styles.shadow)} {...props} />;
     12 +}
     13 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Icon/Icon.tsx
    skipped 4 lines
    5 5  import external from '../../../assets/icons/sprite/external.svg';
    6 6  import arrow from '../../../assets/icons/sprite/arrow.svg';
    7 7  import arrowDown from '../../../assets/icons/sprite/arrow-down.svg';
    8  -import bug from '../../../assets/icons/sprite/bug.svg';
     8 +import vulnerability from '../../../assets/icons/sprite/vulnerability.svg';
    9 9  import logo from '../../../assets/icons/sprite/logo.svg';
    10 10  import weight from '../../../assets/icons/sprite/weight.svg';
    11 11  import search from '../../../assets/icons/sprite/search.svg';
    skipped 13 lines
    25 25  import check from '../../../assets/icons/sprite/check.svg';
    26 26  import arrowBack from '../../../assets/icons/sprite/arrow-back.svg';
    27 27  import filters from '../../../assets/icons/sprite/filters.svg';
     28 +import questionMark from '../../../assets/icons/sprite/question-mark.svg';
    28 29   
    29 30  const icons = {
    30 31   githubLogo,
    skipped 1 lines
    32 33   lines,
    33 34   external,
    34 35   arrow,
    35  - bug,
     36 + vulnerability,
    36 37   logo,
    37 38   weight,
    38 39   search,
    skipped 14 lines
    53 54   check,
    54 55   arrowBack,
    55 56   filters,
     57 + questionMark,
    56 58  };
    57 59   
    58 60  export type IconProps = {
    skipped 35 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/KeywordsList/KeywordsList.tsx
    skipped 1 lines
    2 2  import clsx from 'clsx';
    3 3  import styles from '../SidebarCategory/SidebarCategory.module.scss';
    4 4  import Chip from '../Chip/Chip';
     5 +import ChipGroup from '../ChipGroup/ChipGroup';
    5 6   
    6 7  type Props = {
    7 8   keywordsList: string[];
    skipped 3 lines
    11 12   
    12 13  export default function KeywordsList({ keywordsList, selectedKeywords, selectHandler }: Props) {
    13 14   return (
    14  - <>
     15 + <ChipGroup>
    15 16   {keywordsList.slice(0, 6).map((keyword) => (
    16 17   <Chip
    17 18   key={keyword}
    18  - className={clsx(
    19  - styles.sidebarChip,
    20  - selectedKeywords.includes(keyword) && styles.sidebarChipActive
    21  - )}
     19 + className={clsx(selectedKeywords.includes(keyword) && styles.sidebarChipActive)}
    22 20   onClick={() => selectHandler(keyword)}
    23 21   size='medium'
    24 22   font='monospace'
    skipped 1 lines
    26 24   {keyword}
    27 25   </Chip>
    28 26   ))}
    29  - </>
     27 + </ChipGroup>
    30 28   );
    31 29  }
    32 30   
  • ■ ■ ■ ■ ■
    packages/web/src/components/ui/PackagePreview/PackagePreview.module.scss
    skipped 101 lines
    102 102   display: flex;
    103 103   align-items: center;
    104 104   justify-content: center;
     105 + padding: 0;
    105 106   
    106  - // FIXME: not sure that 24x24 is good UX size for clickable element,
    107  - // i'm considering at least 32x32, or not changing desktop 36x36 at all
    108  - //@include mobile-and-tablet {
    109  - // width: 24px;
    110  - // height: 24px;
    111  - //}
     107 + @include mobile-and-tablet {
     108 + width: 24px;
     109 + height: 24px;
     110 + }
    112 111  }
    113 112   
    114 113  .arrow {
    skipped 1 lines
    116 115   
    117 116   .open & {
    118 117   transform: rotate(180deg);
     118 + }
     119 + 
     120 + @include mobile-and-tablet {
     121 + width: 10px;
     122 + height: 7px;
    119 123   }
    120 124  }
    121 125   
    skipped 43 lines
    165 169   
    166 170  .statIcon {
    167 171   margin-right: 8px;
     172 +}
     173 + 
     174 +.statTooltip {
     175 + margin-left: 8px;
    168 176  }
    169 177   
    170 178  .statLink {
    skipped 92 lines
    263 271   
    264 272  .popularity {
    265 273   margin-top: 16px;
    266  - display: grid;
    267  - grid-template-columns: repeat(6, 1fr);
    268  - grid-column-gap: 11px;
    269  - overflow-x: auto;
    270  - 
    271  - &::-webkit-scrollbar {
    272  - display: none;
    273  - }
    274 274   
    275 275   @include mobile {
    276 276   margin: 0 -20px;
    277 277   padding: 0 20px;
    278  - display: flex;
    279  - flex-wrap: nowrap;
    280  - }
    281  -}
    282  - 
    283  -.popularityItem {
    284  - height: 282px;
    285  - background: $gray-surface;
    286  - border-radius: 12px;
    287  - display: flex;
    288  - flex-direction: column;
    289  - justify-content: flex-end;
    290  - 
    291  - @include mobile-and-tablet {
    292  - min-width: 92px;
    293 278   }
    294  -}
    295  - 
    296  -.popularityFill {
    297  - border-radius: 10px;
    298  - background: $gray-border;
    299  - display: flex;
    300  - flex-direction: column;
    301  - justify-content: flex-end;
    302  - align-items: center;
    303  - font-weight: 500;
    304  - color: $gray-text;
    305  - padding-bottom: 14px;
    306  -}
    307  - 
    308  -.popularityFillSkeleton {
    309  - padding-bottom: 0;
    310  -}
    311  - 
    312  -.popularitySkeleton {
    313  - padding-bottom: 0;
    314  - transform: none;
    315  -}
    316  - 
    317  -.popularityFillAccent {
    318  - background: $blue-accent;
    319  - color: #ffffff;
    320  -}
    321  - 
    322  -.popularityVersion {
    323  - display: flex;
    324  - align-items: center;
    325  - justify-content: center;
    326  - margin-top: 16px;
    327  - font-family: $font-monospace;
    328  - font-weight: 500;
    329  -}
    330  - 
    331  -.popularityVersionSkeleton {
    332  - margin: 15px auto 0;
    333  -}
    334  - 
    335  -.popularityVersionIcon {
    336  - margin-left: 8px;
    337 279  }
    338 280   
    339 281  .usedOnList {
    skipped 138 lines
  • ■ ■ ■ ■ ■
    packages/web/src/components/ui/PackagePreview/PackagePreview.stories.tsx
    skipped 14 lines
    15 15   <PackagePreviewSkeleton />
    16 16  );
    17 17  export const Closed: ComponentStory<typeof PackagePreview> = () => (
    18  - <PackagePreview name='name' version='1.0.0' />
     18 + <PackagePreview
     19 + name='@team-griffin/react-heading-section'
     20 + version='3.0.0 - 4.16.4'
     21 + desc='The Lodash library exported as ES modules. Generated using lodash-cli'
     22 + problems={['vulnerabilities']}
     23 + keywords={['#moment', '#date', '#time', '#parse', '#format', '#format', '#format']}
     24 + author={{ name: 'jdalton', image: 'https://via.placeholder.com/36' }}
     25 + />
    19 26  );
    20 27   
    21 28  export const OpenedLoading: ComponentStory<typeof PackagePreview> = () => (
    22  - <PackagePreview name='name' version='1.0.0' opened detailsLoading />
     29 + <PackagePreview
     30 + name='@team-griffin/react-heading-section'
     31 + version='3.0.0 - 4.16.4'
     32 + desc='The Lodash library exported as ES modules. Generated using lodash-cli'
     33 + problems={['vulnerabilities']}
     34 + keywords={['#moment', '#date', '#time', '#parse', '#format', '#format', '#format']}
     35 + author={{ name: 'jdalton', image: 'https://via.placeholder.com/36' }}
     36 + opened
     37 + detailsLoading
     38 + />
    23 39  );
    24 40   
    25 41  export const Opened: ComponentStory<typeof PackagePreview> = () => (
    26  - <PackagePreview name='name' version='1.0.0' opened />
     42 + <PackagePreview
     43 + name='@team-griffin/react-heading-section'
     44 + version='3.0.0 - 4.16.4'
     45 + desc='The Lodash library exported as ES modules. Generated using lodash-cli'
     46 + problems={['vulnerabilities']}
     47 + keywords={['#moment', '#date', '#time', '#parse', '#format', '#format', '#format']}
     48 + author={{ name: 'jdalton', image: 'https://via.placeholder.com/36' }}
     49 + opened
     50 + />
    27 51  );
    28 52   
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/PackagePreview/PackagePreview.tsx
    skipped 3 lines
    4 4  import Chip from '../Chip/Chip';
    5 5  import clsx from 'clsx';
    6 6  import ChipGroup from '../ChipGroup/ChipGroup';
    7  -import SitesList, { Site } from '../SitesList/SitesList';
     7 +import SitesList from '../SitesList/SitesList';
    8 8  import { CSSTransition } from 'react-transition-group';
    9 9  import Button from '../Button/Button';
    10  -import { formatNumber } from 'utils/helpers';
    11 10  import {
    12 11   LicenceSkeleton,
    13 12   LinksSkeleton,
    14  - PopularitySkeleton,
    15  - PopularityVersionSkeleton,
    16 13   RatingSkeleton,
    17 14   ScriptSkeleton,
    18 15  } from './PackagePreviewSkeleton';
     16 +import ProblemBadge from '../ProblemBadge/ProblemBadge';
     17 +import { ChipGroupSkeleton } from '../ChipGroup/ChipGroupSkeleton';
     18 +import { SitesListSkeleton } from '../SitesList/SitesListSkeleton';
     19 +import BarChart from '../BarChart/BarChart';
     20 +import BarChartSkeleton from '../BarChart/BarChartSkeleton';
     21 +import { formatNumber } from 'utils/helpers';
     22 +import Hint from '../Tooltip/Hint';
     23 + 
     24 +type Problem = 'vulnerabilities' | 'duplicate' | 'outdated';
     25 + 
     26 +type ExternalLink = {
     27 + href: string;
     28 + kind: 'repository' | 'link' | 'npm';
     29 + linkText?: string;
     30 +};
    19 31   
    20 32  type Props = {
    21 33   name: string;
    22 34   version: string;
     35 + desc: string;
     36 + problems?: Problem[];
     37 + keywords: string[];
     38 + author: {
     39 + name: string;
     40 + image: string;
     41 + };
    23 42   opened?: boolean;
    24 43   detailsLoading?: boolean;
    25 44  };
    26 45   
    27 46  // TODO: refactor this (decomposition, props, memoization, etc)
    28  -export default function PackagePreview({ name, version, opened, detailsLoading = false }: Props) {
     47 +export default function PackagePreview({
     48 + name,
     49 + version,
     50 + desc,
     51 + problems,
     52 + keywords,
     53 + author,
     54 + opened,
     55 + detailsLoading = false,
     56 +}: Props) {
    29 57   const [open, setOpen] = useState<boolean>(opened ?? false);
    30 58   const [packageDetailsLoading, setPackageDetailsLoading] = useState<boolean>(detailsLoading);
    31 59   
    32  - // TODO: mock data, remove later
    33  - const sites: Site[] = [
    34  - {
    35  - id: '123',
    36  - image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    37  - name: 'pinterest.com',
    38  - packagesCount: 151,
    39  - },
    40  - {
    41  - id: '456',
    42  - image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    43  - name: 'pinterest.com',
    44  - packagesCount: 151,
    45  - },
    46  - {
    47  - id: '789',
    48  - image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    49  - name: 'pinterest.com',
    50  - packagesCount: 151,
    51  - },
    52  - {
    53  - id: '1231',
    54  - image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    55  - name: 'pinterest.com',
    56  - packagesCount: 151,
    57  - },
    58  - {
    59  - id: '12321',
    60  - image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    61  - name: 'pinterest.com',
    62  - packagesCount: 151,
    63  - },
    64  - {
    65  - id: '123123',
    66  - image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    67  - name: 'pinterest.com',
    68  - packagesCount: 151,
    69  - },
    70  - {
    71  - id: '12123132',
    72  - image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
    73  - name: 'pinterest.com',
    74  - packagesCount: 151,
    75  - },
    76  - ];
    77  - 
    78  - // TODO: mock data, remove later
    79  - const modules = [
    80  - {
    81  - fill: '100%',
    82  - uses: 89912,
    83  - moduleVersion: '21.3.0',
    84  - },
    85  - {
    86  - fill: '90%',
    87  - uses: 67111,
    88  - moduleVersion: '18.2.0',
    89  - },
    90  - {
    91  - fill: '80%',
    92  - uses: 44212,
    93  - moduleVersion: '20.1.0',
    94  - },
    95  - {
    96  - fill: '70%',
    97  - uses: 41129,
    98  - moduleVersion: '18.0.0',
    99  - },
    100  - {
    101  - fill: '60%',
    102  - uses: 40465,
    103  - moduleVersion: '19.11.2',
    104  - },
    105  - {
    106  - fill: '50%',
    107  - uses: 38907,
    108  - moduleVersion: '8.1.2',
    109  - bug: true,
    110  - },
    111  - ];
    112  - 
    113 60   const toggleOpen = () => {
    114 61   if (open) {
    115 62   setOpen(false);
    skipped 2 lines
    118 65   setPackageDetailsLoading(true);
    119 66   
    120 67   // FIXME: just for demo purposes
    121  - setTimeout(() => setPackageDetailsLoading(false), 60000);
     68 + setTimeout(() => setPackageDetailsLoading(false), 4000);
    122 69   }
    123 70   };
    124 71   
     72 + // TODO: Mock API data, remove later
     73 + const externalLinks: ExternalLink[] = [
     74 + { kind: 'repository', href: 'https://github.com/facebook/react/', linkText: 'Repository' },
     75 + { kind: 'link', href: 'https://reactjs.org/', linkText: 'Homepage' },
     76 + { kind: 'npm', href: 'https://www.npmjs.com/package/react' },
     77 + ];
     78 + 
     79 + // TODO: Mock API data, remove later
     80 + const loadedData = {
     81 + script: '/rsrc.php/v3id044/yu/l/en_US/yD2XaVkWQHO.js?_nc_x=Ij3Wp8lg5Kz',
     82 + license: {
     83 + title: 'MIT license',
     84 + subtitle: 'freely distributable',
     85 + },
     86 + rating: {
     87 + place: 385,
     88 + rankingDelta: -4,
     89 + out: 12842,
     90 + },
     91 + dependencies: ['art', 'create-react-class', 'loose-envify', 'scheduler'],
     92 + packages: [
     93 + {
     94 + fill: 1,
     95 + uses: 89912,
     96 + moduleVersion: '21.3.0',
     97 + },
     98 + {
     99 + fill: 0.8,
     100 + uses: 67111,
     101 + moduleVersion: '18.2.0',
     102 + highlighted: true,
     103 + },
     104 + {
     105 + fill: 0.7,
     106 + uses: 44212,
     107 + moduleVersion: '20.1.0',
     108 + },
     109 + {
     110 + fill: 0.6,
     111 + uses: 41129,
     112 + moduleVersion: '18.0.0',
     113 + },
     114 + {
     115 + fill: 0.5,
     116 + uses: 40465,
     117 + moduleVersion: '19.11.2',
     118 + },
     119 + {
     120 + fill: 0.4,
     121 + uses: 38907,
     122 + moduleVersion: '8.1.2',
     123 + vulnerabilities: true,
     124 + },
     125 + ],
     126 + sites: [
     127 + {
     128 + id: '123',
     129 + image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     130 + name: 'pinterest.com',
     131 + packagesCount: 151,
     132 + },
     133 + {
     134 + id: '456',
     135 + image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     136 + name: 'pinterest.com',
     137 + packagesCount: 151,
     138 + },
     139 + {
     140 + id: '789',
     141 + image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     142 + name: 'pinterest.com',
     143 + packagesCount: 151,
     144 + },
     145 + {
     146 + id: '1231',
     147 + image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     148 + name: 'pinterest.com',
     149 + packagesCount: 151,
     150 + },
     151 + {
     152 + id: '12321',
     153 + image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     154 + name: 'pinterest.com',
     155 + packagesCount: 151,
     156 + },
     157 + {
     158 + id: '123123',
     159 + image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     160 + name: 'pinterest.com',
     161 + packagesCount: 151,
     162 + },
     163 + {
     164 + id: '12123132',
     165 + image: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     166 + name: 'pinterest.com',
     167 + packagesCount: 151,
     168 + },
     169 + ],
     170 + links: externalLinks,
     171 + };
     172 + 
     173 + const { script, license, rating, dependencies, packages, sites, links } = loadedData;
     174 + 
    125 175   return (
    126 176   <div className={clsx(styles.package, open && styles.open)}>
    127  - <header className={styles.header}>
     177 + <div className={styles.header}>
    128 178   <div className={styles.top} onClick={toggleOpen}>
    129 179   <div className={styles.title}>
    130 180   <span className={styles.name}>
    131 181   {name} <span className={styles.version}>{version}</span>
    132 182   </span>
    133  - <span className={styles.problems}>
    134  - <Chip
    135  - variant='vulnerability'
    136  - size='badge'
    137  - icon={<Icon kind='bug' width={24} height={24} color='white' />}
    138  - >
    139  - Vulnerabilities
    140  - </Chip>
    141  - <Chip
    142  - variant='duplicate'
    143  - size='badge'
    144  - icon={<Icon kind='duplicate' width={24} height={24} color='white' />}
    145  - >
    146  - Duplicate
    147  - </Chip>
    148  - <Chip
    149  - variant='outdated'
    150  - size='badge'
    151  - icon={
    152  - <Icon kind='outdated' width={24} height={24} color='white' stroke='#F1CE61' />
    153  - }
    154  - >
    155  - Outdated
    156  - </Chip>
    157  - </span>
     183 + 
     184 + {problems && (
     185 + <span className={styles.problems}>
     186 + {problems.map((problem) => (
     187 + <ProblemBadge key={problem} problem={problem} />
     188 + ))}
     189 + </span>
     190 + )}
    158 191   </div>
    159 192   
    160 193   <button type='button' className={styles.arrowWrapper} onClick={toggleOpen}>
    161  - {/* FIXME: requires different smaller svg icon for mobile and makes button 24x24 */}
    162  - {/* which is probably not optimal UX */}
    163 194   <Icon kind='arrowDown' width={14} height={8} color='#8E8AA0' className={styles.arrow} />
    164 195   </button>
    165 196   </div>
    166 197   
    167  - <div className={styles.desc}>
    168  - The Lodash library exported as ES modules. Generated using lodash-cli
    169  - </div>
    170  - </header>
     198 + <div className={styles.desc}>{desc}</div>
     199 + </div>
    171 200   
    172 201   <CSSTransition
    173 202   in={open}
    skipped 13 lines
    187 216   <ScriptSkeleton />
    188 217   ) : (
    189 218   <a href='#' className={styles.statLink} target='_blank' rel='noreferrer'>
    190  - /rsrc.php/v3id044/yu/l/en_US/yD2XaVkWQHO.js?_nc_x=Ij3Wp8lg5Kz
     219 + {script}
    191 220   </a>
    192 221   )}
    193 222   </div>
    skipped 8 lines
    202 231   <LicenceSkeleton />
    203 232   ) : (
    204 233   <>
    205  - <div className={styles.statTitle}>MIT license</div>
    206  - <div className={styles.statSubtitle}>freely distributable</div>
     234 + <div className={styles.statTitle}>{license.title}</div>
     235 + <div className={styles.statSubtitle}>{license.subtitle}</div>
    207 236   </>
    208 237   )}
    209 238   </div>
    skipped 2 lines
    212 241   <div className={styles.statHeader}>
    213 242   <Icon kind='rating' color='#8E8AA0' className={styles.statIcon} />
    214 243   Rating
     244 + <span className={styles.statTooltip}>
     245 + <Hint text='Rating based on our service' />
     246 + </span>
    215 247   </div>
    216 248   {packageDetailsLoading ? (
    217 249   <RatingSkeleton />
    218 250   ) : (
    219  - // TODO: What about adding a rankingDelta prop and deciding on class name based on number's sign?
    220 251   <>
    221 252   <div className={styles.statTitle}>
    222  - 385
    223  - {/* or: <div className={clsx(styles.statRating, styles.statRatingRed)}> */}
    224  - <div className={clsx(styles.statRating, styles.statRatingGreen)}>
     253 + {rating.place}
     254 + 
     255 + <div
     256 + className={clsx(
     257 + styles.statRating,
     258 + rating.rankingDelta > 0 ? styles.statRatingGreen : styles.statRatingRed
     259 + )}
     260 + >
    225 261   <Icon
    226 262   kind='ratingArrow'
    227 263   width={12}
    228 264   height={12}
    229 265   className={styles.statRatingArrow}
    230 266   />
    231  - +4
     267 + {rating.rankingDelta}
    232 268   </div>
    233 269   </div>
    234  - <div className={styles.statSubtitle}>out of 12 842</div>
     270 + <div className={styles.statSubtitle}>out of {formatNumber(rating.out)}</div>
    235 271   </>
    236 272   )}
    237 273   </div>
    skipped 1 lines
    239 275   <div className={clsx(styles.stat, styles.statListItemLarge)}>
    240 276   <div className={styles.statHeader}>
    241 277   <Icon kind='dependency' color='#8E8AA0' className={styles.statIcon} />
    242  - {!packageDetailsLoading && 4} Dependency
     278 + Dependencies
    243 279   </div>
    244  - <ChipGroup
    245  - chips={['art', 'create-react-class', 'loose-envify', 'scheduler']}
    246  - fontSize='small'
    247  - loading={packageDetailsLoading}
    248  - />
     280 + {packageDetailsLoading ? (
     281 + <ChipGroupSkeleton />
     282 + ) : (
     283 + <ChipGroup>
     284 + {dependencies.map((dependency) => (
     285 + <Chip size='medium' fontSize='small' font='monospace'>
     286 + {dependency}
     287 + </Chip>
     288 + ))}
     289 + </ChipGroup>
     290 + )}
    249 291   </div>
    250 292   </div>
    251 293   
    skipped 4 lines
    256 298   </div>
    257 299   
    258 300   <div className={styles.popularity}>
    259  - {modules.map(({ fill, uses, moduleVersion, bug }) => (
    260  - <div className={styles.popularityItemWrapper}>
    261  - <div className={styles.popularityItem}>
    262  - {packageDetailsLoading ? (
    263  - // TODO: We should be on lookout for these deoptimizations,
    264  - // this should definitely be a component / top-level const.
    265  - <div
    266  - className={clsx(styles.popularityFill, styles.popularityFillSkeleton)}
    267  - style={{ height: fill }}
    268  - >
    269  - <PopularitySkeleton />
    270  - </div>
    271  - ) : (
    272  - <div className={styles.popularityFill} style={{ height: fill }}>
    273  - {formatNumber(uses)}
    274  - </div>
    275  - )}
    276  - </div>
    277  - 
    278  - {packageDetailsLoading ? (
    279  - <PopularityVersionSkeleton />
    280  - ) : (
    281  - <div className={styles.popularityVersion}>
    282  - {moduleVersion}
    283  - {bug && (
    284  - <Icon
    285  - kind='bugOutlined'
    286  - color='#212121'
    287  - className={styles.popularityVersionIcon}
    288  - />
    289  - )}
    290  - </div>
    291  - )}
    292  - </div>
    293  - ))}
     301 + {packageDetailsLoading ? <BarChartSkeleton /> : <BarChart bars={packages} />}
    294 302   </div>
    295 303   </div>
    296 304   
    skipped 2 lines
    299 307   <div className={styles.stat}>
    300 308   <div className={styles.statHeader}>Used on</div>
    301 309   
    302  - <SitesList
    303  - sites={sites}
    304  - className={styles.usedOnList}
    305  - loading={packageDetailsLoading}
    306  - />
     310 + {packageDetailsLoading ? (
     311 + <SitesListSkeleton className={styles.usedOnList} />
     312 + ) : (
     313 + <SitesList sites={sites} className={styles.usedOnList} />
     314 + )}
    307 315   </div>
    308 316   
    309 317   <div className={styles.actions}>
    skipped 1 lines
    311 319   {packageDetailsLoading ? (
    312 320   <LinksSkeleton />
    313 321   ) : (
    314  - <>
    315  - <a href='#' className={styles.link} target='_blank' rel='noreferrer'>
    316  - <Icon kind='repository' color='#212121' className={styles.linkIcon} />
    317  - Repository
    318  - </a>
    319  - 
    320  - <a href='#' className={styles.link} target='_blank' rel='noreferrer'>
    321  - <Icon kind='link' color='#212121' className={styles.linkIcon} />
    322  - Homepage
    323  - </a>
    324  - 
    325  - <a href='#' className={styles.link} target='_blank' rel='noreferrer'>
     322 + links.map(({ href, kind, linkText }) => (
     323 + <a
     324 + key={href}
     325 + href={href}
     326 + className={styles.link}
     327 + target='_blank'
     328 + rel='noreferrer'
     329 + >
    326 330   <Icon
    327  - kind='npm'
    328  - width={32}
    329  - height={32}
     331 + kind={kind}
     332 + width={kind !== 'npm' ? 16 : 32}
     333 + height={kind !== 'npm' ? 16 : 32}
    330 334   color='#212121'
    331 335   className={styles.linkIcon}
    332 336   />
     337 + {linkText}
    333 338   </a>
    334  - </>
     339 + ))
    335 340   )}
    336 341   </div>
    337 342   
    skipped 3 lines
    341 346   </div>
    342 347   </CSSTransition>
    343 348   
    344  - <footer className={styles.footer}>
     349 + <div className={styles.footer}>
    345 350   <div className={styles.tags}>
    346  - <a href='#' className={styles.tag}>
    347  - #moment
    348  - </a>
    349  - <a href='#' className={styles.tag}>
    350  - #date
    351  - </a>
    352  - <a href='#' className={styles.tag}>
    353  - #time
    354  - </a>
    355  - <a href='#' className={styles.tag}>
    356  - #parse
    357  - </a>
    358  - <a href='#' className={styles.tag}>
    359  - #format
    360  - </a>
    361  - <Chip variant='info' size='medium' fontWeight='semiBold'>
    362  - +45
    363  - </Chip>
     351 + {/* TODO: not sure how to conditionally render maximum number of keywords (e.g. 5 for
     352 + desktop, 3/4 for tablet, 2 for mobile) based on viewport and update rest number
     353 + of keywords beyond current maximum in Chip */}
     354 + {keywords.slice(0, 5).map((keyword) => (
     355 + <a key={keyword} href='#' className={styles.tag}>
     356 + {keyword}
     357 + </a>
     358 + ))}
     359 + {keywords.slice(5).length > 0 && (
     360 + <Chip variant='info' size='medium' fontWeight='semiBold'>
     361 + +{keywords.slice(5).length}
     362 + </Chip>
     363 + )}
    364 364   </div>
    365 365   
    366 366   <div className={styles.author}>
    367  - <span className={styles.authorName}>jdalton</span>
    368  - <img className={styles.authorImage} src='https://via.placeholder.com/36' alt='' />
     367 + <span className={styles.authorName}>{author.name}</span>
     368 + <img className={styles.authorImage} src={author.image} alt='' />
    369 369   </div>
    370  - </footer>
     370 + </div>
    371 371   </div>
    372 372   );
    373 373  }
    skipped 1 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/PackagePreview/PackagePreviewSkeleton.tsx
    skipped 20 lines
    21 21   </>
    22 22  );
    23 23   
    24  -export const PopularitySkeleton = () => (
    25  - <Skeleton width='100%' height='100%' className={styles.popularitySkeleton} />
    26  -);
    27  - 
    28  -export const PopularityVersionSkeleton = () => (
    29  - <Skeleton width={64} className={styles.popularityVersionSkeleton} />
    30  -);
    31  - 
    32 24  export const LinksSkeleton = () => (
    33 25   <>
    34 26   {repeat(
    skipped 9 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/ProblemBadge/ProblemBadge.tsx
     1 +import React from 'react';
     2 +import Chip from '../Chip/Chip';
     3 +import { Icon } from '../Icon/Icon';
     4 + 
     5 +type Props = {
     6 + problem: 'vulnerabilities' | 'duplicate' | 'outdated';
     7 +};
     8 + 
     9 +function ProblemBadge({ problem }: Props) {
     10 + switch (problem) {
     11 + case 'vulnerabilities':
     12 + return (
     13 + <Chip
     14 + variant='vulnerabilities'
     15 + size='badge'
     16 + icon={<Icon kind='vulnerability' width={24} height={24} color='white' />}
     17 + >
     18 + Vulnerabilities
     19 + </Chip>
     20 + );
     21 + case 'duplicate':
     22 + return (
     23 + <Chip
     24 + variant='duplicate'
     25 + size='badge'
     26 + icon={<Icon kind='duplicate' width={24} height={24} color='white' />}
     27 + >
     28 + Duplicate
     29 + </Chip>
     30 + );
     31 + case 'outdated':
     32 + return (
     33 + <Chip
     34 + variant='outdated'
     35 + size='badge'
     36 + icon={<Icon kind='outdated' width={24} height={24} color='white' stroke='#F1CE61' />}
     37 + >
     38 + Outdated
     39 + </Chip>
     40 + );
     41 + }
     42 +}
     43 + 
     44 +export default ProblemBadge;
     45 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/SidebarCategory/SidebarCategory.module.scss
    skipped 72 lines
    73 73   }
    74 74  }
    75 75   
    76  -.sidebarChip {
    77  - margin-bottom: 8px;
    78  - 
    79  - &:not(:last-child) {
    80  - margin-right: 8px;
    81  - }
    82  -}
    83  - 
    84 76  .sidebarChipActive {
    85 77   background-color: $black;
    86 78   color: $white;
    skipped 6 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/SitesList/SitesList.tsx
    1 1  import React from 'react';
    2 2  import styles from './SitesList.module.scss';
    3 3  import clsx from 'clsx';
    4  -import { SitesListSkeleton } from './SitesListSkeleton';
    5 4   
    6 5  export type Site = {
    7 6   id?: string;
    8 7   image?: string;
    9 8   name?: string;
    10 9   packagesCount?: number;
    11  - loading?: boolean;
    12 10  };
    13 11   
    14 12  type Props = {
    15 13   sites: Site[];
    16 14   className?: string;
    17  - loading?: boolean;
    18 15  };
    19 16   
    20 17  function Site({ image, name, packagesCount }: Site) {
    skipped 10 lines
    31 28   );
    32 29  }
    33 30   
    34  -export default function SitesList({ sites, className, loading }: Props) {
     31 +export default function SitesList({ sites, className }: Props) {
    35 32   return (
    36 33   <div className={styles.sitesListWrapper}>
    37 34   <div className={clsx(styles.sitesList, className)}>
    38  - {loading ? <SitesListSkeleton /> : sites.map((site) => <Site key={site.id} {...site} />)}
     35 + {sites.map((site) => (
     36 + <Site key={site.id} {...site} />
     37 + ))}
    39 38   </div>
    40 39   </div>
    41 40   );
    skipped 2 lines
  • ■ ■ ■ ■ ■
    packages/web/src/components/ui/SitesList/SitesListSkeleton.tsx
    skipped 1 lines
    2 2  import styles from './SitesList.module.scss';
    3 3  import Skeleton from '../Skeleton/Skeleton';
    4 4  import { repeat } from '../../../utils/helpers';
     5 +import clsx from 'clsx';
    5 6   
    6 7  export const SiteSkeleton = () => (
    7 8   <div className={styles.site}>
    skipped 7 lines
    15 16   </div>
    16 17  );
    17 18   
    18  -export const SitesListSkeleton = () => <>{repeat(4, <SiteSkeleton />)}</>;
     19 +type Props = {
     20 + className?: string;
     21 +};
     22 + 
     23 +export const SitesListSkeleton = ({ className }: Props) => (
     24 + <div className={styles.sitesListWrapper}>
     25 + <div className={clsx(styles.sitesList, className)}>{repeat(4, <SiteSkeleton />)}</div>
     26 + </div>
     27 +);
    19 28   
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Tooltip/Hint.tsx
     1 +import React from 'react';
     2 +import styles from './Tooltip.module.scss';
     3 +import { usePopperTooltip, Config } from 'react-popper-tooltip';
     4 +import { Icon } from '../Icon/Icon';
     5 + 
     6 +type Props = {
     7 + text: string;
     8 +};
     9 + 
     10 +const tooltipOptions: Config = { placement: 'top-end', offset: [20, 13] };
     11 + 
     12 +export default function Hint({ text }: Props) {
     13 + const { getArrowProps, getTooltipProps, setTooltipRef, setTriggerRef, visible } =
     14 + usePopperTooltip(tooltipOptions);
     15 + 
     16 + return (
     17 + <>
     18 + <span className={styles.tooltipTrigger} ref={setTriggerRef}>
     19 + <Icon kind='questionMark' width={6} height={11} stroke='white' color='white' />
     20 + </span>
     21 + 
     22 + {visible && (
     23 + <div ref={setTooltipRef} {...getTooltipProps({ className: styles.tooltipContainer })}>
     24 + {text}
     25 + <div {...getArrowProps({ className: styles.tooltipArrow })} />
     26 + </div>
     27 + )}
     28 + </>
     29 + );
     30 +}
     31 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Tooltip/Tooltip.module.scss
     1 +@import '~styles/_vars.scss';
     2 + 
     3 +.tooltipTrigger {
     4 + width: 16px;
     5 + height: 16px;
     6 + border-radius: 50%;
     7 + background: $gray-text;
     8 + display: flex;
     9 + align-items: center;
     10 + justify-content: center;
     11 +}
     12 + 
     13 +.tooltipContainer {
     14 + background-color: $black;
     15 + border-radius: 10px;
     16 + box-shadow: 0 0 4px rgba(0, 0, 0, 0.04), 0 4px 32px rgba(0, 0, 0, 0.16);
     17 + color: $white;
     18 + display: flex;
     19 + flex-direction: column;
     20 + padding: 9px 12px;
     21 + font-size: 14px;
     22 + line-height: 20px;
     23 + transition: opacity 0.3s;
     24 + z-index: 9999;
     25 + max-width: 130px;
     26 +}
     27 + 
     28 +.tooltipContainer[data-popper-interactive='false'] {
     29 + pointer-events: none;
     30 +}
     31 + 
     32 +.tooltipArrow {
     33 + position: absolute;
     34 + width: 1rem;
     35 + height: 1rem;
     36 + pointer-events: none;
     37 +}
     38 + 
     39 +.tooltipArrow::before {
     40 + border-style: solid;
     41 + content: '';
     42 + display: block;
     43 + height: 0;
     44 + margin: auto;
     45 + width: 0;
     46 +}
     47 + 
     48 +.tooltipArrow::after {
     49 + border-style: solid;
     50 + content: '';
     51 + display: block;
     52 + height: 0;
     53 + margin: auto;
     54 + position: absolute;
     55 + width: 0;
     56 +}
     57 + 
     58 +.tooltipContainer[data-popper-placement*='bottom'] .tooltipArrow {
     59 + left: 0;
     60 + margin-top: -0.4rem;
     61 + top: 0;
     62 +}
     63 + 
     64 +.tooltipContainer[data-popper-placement*='bottom'] .tooltipArrow::before {
     65 + border-color: transparent transparent $black transparent;
     66 + border-width: 0 0.5rem 0.4rem 0.5rem;
     67 + position: absolute;
     68 + top: -1px;
     69 +}
     70 + 
     71 +.tooltipContainer[data-popper-placement*='bottom'] .tooltipArrow::after {
     72 + border-color: transparent transparent $black transparent;
     73 + border-width: 0 0.5rem 0.4rem 0.5rem;
     74 +}
     75 + 
     76 +.tooltipContainer[data-popper-placement*='top'] .tooltipArrow {
     77 + bottom: 0;
     78 + left: 0;
     79 + margin-bottom: -1rem;
     80 +}
     81 + 
     82 +.tooltipContainer[data-popper-placement*='top'] .tooltipArrow::before {
     83 + border-color: $black transparent transparent transparent;
     84 + border-width: 0.5rem 0.5rem 0 0.5rem;
     85 + position: absolute;
     86 + top: 0;
     87 +}
     88 + 
     89 +.tooltipContainer[data-popper-placement*='top'] .tooltipArrow::after {
     90 + border-color: $black transparent transparent transparent;
     91 + border-width: 0.5rem 0.5rem 0 0.5rem;
     92 +}
     93 + 
     94 +.tooltipContainer[data-popper-placement*='right'] .tooltipArrow {
     95 + left: 0;
     96 + margin-left: -0.7rem;
     97 +}
     98 + 
     99 +.tooltipContainer[data-popper-placement*='right'] .tooltipArrow::before {
     100 + border-color: transparent $black transparent transparent;
     101 + border-width: 0.5rem 0.4rem 0.5rem 0;
     102 +}
     103 + 
     104 +.tooltipContainer[data-popper-placement*='right'] .tooltipArrow::after {
     105 + border-color: transparent $black transparent transparent;
     106 + border-width: 0.5rem 0.4rem 0.5rem 0;
     107 + left: 6px;
     108 + top: 0;
     109 +}
     110 + 
     111 +.tooltipContainer[data-popper-placement*='left'] .tooltipArrow {
     112 + margin-right: -0.7rem;
     113 + right: 0;
     114 +}
     115 + 
     116 +.tooltipContainer[data-popper-placement*='left'] .tooltipArrow::before {
     117 + border-color: transparent transparent transparent $black;
     118 + border-width: 0.5rem 0 0.5rem 0.4em;
     119 +}
     120 + 
     121 +.tooltipContainer[data-popper-placement*='left'] .tooltipArrow::after {
     122 + border-color: transparent transparent transparent $black;
     123 + border-width: 0.5rem 0 0.5rem 0.4em;
     124 + left: 3px;
     125 + top: 0;
     126 +}
     127 + 
  • ■ ■ ■ ■
    packages/web/src/components/ui/VulnerablePackage/VulnerablePackage.tsx
    skipped 12 lines
    13 13   <div className={styles.vulnerableWrapper}>
    14 14   <span className={styles.vulnerablePackage}>
    15 15   <span className={styles.vulnerableIcon}>
    16  - <Icon kind='bug' color='#F3512E' width={28} height={28} />
     16 + <Icon kind='vulnerability' color='#F3512E' width={28} height={28} />
    17 17   </span>
    18 18   {name}
    19 19   </span>
    skipped 13 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/hooks/useScrollPosition.tsx
     1 +import { useEffect, useState } from 'react';
     2 +import throttle from 'lodash.throttle';
     3 + 
     4 +export const useScrollPosition = () => {
     5 + const [scrollPosition, setScrollPosition] = useState(0);
     6 + 
     7 + useEffect(() => {
     8 + const updatePosition = () => {
     9 + setScrollPosition(window.scrollY);
     10 + };
     11 + const throttledUpdate = throttle(updatePosition, 250);
     12 + 
     13 + window.addEventListener('scroll', throttledUpdate);
     14 + 
     15 + throttledUpdate();
     16 + 
     17 + return () => window.removeEventListener('scroll', throttledUpdate);
     18 + }, []);
     19 + 
     20 + return scrollPosition;
     21 +};
     22 + 
  • ■ ■ ■ ■ ■ ■
    packages/web/src/mocks/CardListsMocks.ts
     1 +import { KeyedPackagesBySourceCardProps } from '../components/ui/CardList/PackagesBySourceCardList';
     2 +import { KeyedPopularPackageCardProps } from '../components/ui/CardList/PopularPackageCardList';
     3 +import { KeyedScansWithVulnerabilitiesCardProps } from '../components/ui/CardList/ScansWithVulnerabilitiesCardList';
     4 + 
     5 +export const packagesBySourceListData: KeyedPackagesBySourceCardProps[] = [
     6 + {
     7 + id: 'uExBVGuF',
     8 + sourceTitle: 'github.com',
     9 + sourceIcon: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     10 + packages: ['mdast-util-from-markdown', 'react', 'react-dom'],
     11 + morePackagesCount: 45,
     12 + },
     13 + {
     14 + id: '1EkL1u5g',
     15 + sourceTitle: 'fingerprint.com',
     16 + sourceIcon: 'https://avatars.githubusercontent.com/u/67208791?s=200&v=4',
     17 + packages: ['mdast-util-from-markdown', 'react', 'react-dom'],
     18 + morePackagesCount: 45,
     19 + },
     20 + {
     21 + id: 'mhwO2bPM',
     22 + sourceTitle: 'facebook.com',
     23 + sourceIcon: 'https://avatars.githubusercontent.com/u/69631?s=200&v=4',
     24 + packages: ['react'],
     25 + morePackagesCount: 45,
     26 + },
     27 +];
     28 + 
     29 +export const popularPackageListData: KeyedPopularPackageCardProps[] = [
     30 + {
     31 + id: 'FPsBcl8R',
     32 + packageName: '@team-griffin/react-heading-section',
     33 + packageDescription: "This package's job is to automatically determine...",
     34 + hostsFaviconList: [
     35 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     36 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     37 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     38 + ],
     39 + totalUsageCount: 5265,
     40 + },
     41 + {
     42 + id: 'emtYcsUh',
     43 + packageName: '@team-griffin/react-heading-section',
     44 + packageDescription: "This package's job is to automatically determine...",
     45 + hostsFaviconList: [
     46 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     47 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     48 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     49 + ],
     50 + totalUsageCount: 5265,
     51 + },
     52 + {
     53 + id: 'TYIwvAfy',
     54 + packageName: '@team-griffin/react-heading-section',
     55 + packageDescription: "This package's job is to automatically determine...",
     56 + hostsFaviconList: [
     57 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     58 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     59 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     60 + ],
     61 + totalUsageCount: 5265,
     62 + },
     63 + {
     64 + id: 'Lq1pEEX7',
     65 + packageName: '@team-griffin/react-heading-section',
     66 + packageDescription: "This package's job is to automatically determine...",
     67 + hostsFaviconList: [
     68 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     69 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     70 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     71 + ],
     72 + totalUsageCount: 5265,
     73 + },
     74 + {
     75 + id: 'cWOgIbmp',
     76 + packageName: '@team-griffin/react-heading-section',
     77 + packageDescription: "This package's job is to automatically determine...",
     78 + hostsFaviconList: [
     79 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     80 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     81 + 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
     82 + ],
     83 + totalUsageCount: 5265,
     84 + },
     85 +];
     86 + 
     87 +export const scansWithVulnerabilitiesListData: KeyedScansWithVulnerabilitiesCardProps[] = [
     88 + {
     89 + id: 'LnO9Xynn',
     90 + sourcePageUrl: 'disneyland.omsk.ru/signup',
     91 + vulnerablePackageName: 'mdast-util-from-markdown',
     92 + },
     93 + {
     94 + id: '-A74UAy8',
     95 + sourcePageUrl: 'disneyland.omsk.ru/signup',
     96 + vulnerablePackageName: 'mdast-util-from-markdown',
     97 + additionalVulnerabilitiesCount: 1,
     98 + },
     99 + {
     100 + id: 'DPa05I2W',
     101 + sourcePageUrl: 'disneyland.omsk.ru/signup',
     102 + vulnerablePackageName: 'mdast-util-from-markdown',
     103 + },
     104 +];
     105 + 
  • ■ ■ ■ ■
    packages/web/src/utils/helpers.tsx
    1 1  import React from 'react';
    2 2   
    3 3  export function formatNumber(x: number) {
    4  - return x.toLocaleString();
     4 + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
    5 5  }
    6 6   
    7 7  export function repeat(times: number, children: React.ReactNode) {
    skipped 5 lines
  • ■ ■ ■ ■ ■ ■
    yarn.lock
    skipped 1725 lines
    1726 1726   dependencies:
    1727 1727   regenerator-runtime "^0.13.4"
    1728 1728   
     1729 +"@babel/runtime@^7.18.3":
     1730 + version "7.19.0"
     1731 + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259"
     1732 + integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==
     1733 + dependencies:
     1734 + regenerator-runtime "^0.13.4"
     1735 + 
    1729 1736  "@babel/template@^7.12.7", "@babel/template@^7.16.7", "@babel/template@^7.3.3":
    1730 1737   version "7.16.7"
    1731 1738   resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
    skipped 624 lines
    2356 2363   loader-utils "^2.0.0"
    2357 2364   schema-utils "^3.0.0"
    2358 2365   source-map "^0.7.3"
     2366 + 
     2367 +"@popperjs/core@^2.11.5":
     2368 + version "2.11.6"
     2369 + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45"
     2370 + integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==
    2359 2371   
    2360 2372  "@popperjs/core@^2.5.4", "@popperjs/core@^2.6.0":
    2361 2373   version "2.11.4"
    skipped 1421 lines
    3783 3795   version "4.1.7"
    3784 3796   resolved "https://registry.yarnpkg.com/@types/lodash.memoize/-/lodash.memoize-4.1.7.tgz#aff94ab32813c557cbc1104e127030e3d60a3b27"
    3785 3797   integrity sha512-lGN7WeO4vO6sICVpf041Q7BX/9k1Y24Zo3FY0aUezr1QlKznpjzsDk3T3wvH8ofYzoK0QupN9TWcFAFZlyPwQQ==
     3798 + dependencies:
     3799 + "@types/lodash" "*"
     3800 + 
     3801 +"@types/lodash.throttle@^4.1.7":
     3802 + version "4.1.7"
     3803 + resolved "https://registry.yarnpkg.com/@types/lodash.throttle/-/lodash.throttle-4.1.7.tgz#4ef379eb4f778068022310ef166625f420b6ba58"
     3804 + integrity sha512-znwGDpjCHQ4FpLLx19w4OXDqq8+OvREa05H89obtSyXyOFKL3dDjCslsmfBz0T2FU8dmf5Wx1QvogbINiGIu9g==
    3786 3805   dependencies:
    3787 3806   "@types/lodash" "*"
    3788 3807   
    skipped 6583 lines
    10372 10391   resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
    10373 10392   integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
    10374 10393   
     10394 +lodash.throttle@^4.1.1:
     10395 + version "4.1.1"
     10396 + resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
     10397 + integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==
     10398 + 
    10375 10399  [email protected]:
    10376 10400   version "4.5.0"
    10377 10401   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
    skipped 2194 lines
    12572 12596   "@popperjs/core" "^2.5.4"
    12573 12597   react-popper "^2.2.4"
    12574 12598   
     12599 +react-popper-tooltip@^4.4.2:
     12600 + version "4.4.2"
     12601 + resolved "https://registry.yarnpkg.com/react-popper-tooltip/-/react-popper-tooltip-4.4.2.tgz#0dc4894b8e00ba731f89bd2d30584f6032ec6163"
     12602 + integrity sha512-y48r0mpzysRTZAIh8m2kpZ8S1YPNqGtQPDrlXYSGvDS1c1GpG/NUXbsbIdfbhXfmSaRJuTcaT6N1q3CKuHRVbg==
     12603 + dependencies:
     12604 + "@babel/runtime" "^7.18.3"
     12605 + "@popperjs/core" "^2.11.5"
     12606 + react-popper "^2.3.0"
     12607 + 
    12575 12608  react-popper@^2.2.4:
    12576 12609   version "2.2.5"
    12577 12610   resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.2.5.tgz#1214ef3cec86330a171671a4fbcbeeb65ee58e96"
    12578 12611   integrity sha512-kxGkS80eQGtLl18+uig1UIf9MKixFSyPxglsgLBxlYnyDf65BiY9B3nZSc6C9XUNDgStROB0fMQlTEz1KxGddw==
     12612 + dependencies:
     12613 + react-fast-compare "^3.0.1"
     12614 + warning "^4.0.2"
     12615 + 
     12616 +react-popper@^2.3.0:
     12617 + version "2.3.0"
     12618 + resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba"
     12619 + integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==
    12579 12620   dependencies:
    12580 12621   react-fast-compare "^3.0.1"
    12581 12622   warning "^4.0.2"
    skipped 3159 lines
Please wait...
Page is in error, reload to recover