From c6a90fca2008769bf1b783d0413d05c4c91af0dd Mon Sep 17 00:00:00 2001 From: calvin-codecov Date: Wed, 4 Dec 2024 09:56:16 -0800 Subject: [PATCH] refactor: Convert UI files to TS (#3475) --- .storybook/preview.tsx | 11 +- .../ManageAdminCard/AdminTable/AdminTable.tsx | 1 - .../EmailAddress/EmailAddress.tsx | 2 - .../TriggerSyncBanner/TriggerSyncBanner.tsx | 2 +- .../TriggerSyncBanner/TriggerSyncBanner.tsx | 2 +- src/shared/AppLink/AppLink.jsx | 1 + .../LicenseExpirationModal.tsx | 1 - .../ReposTableTeam/ReposTableTeam.tsx | 2 +- src/ui/Avatar/Avatar.jsx | 2 +- src/ui/Button/Button.stories.jsx | 89 ------------ src/ui/Button/Button.stories.tsx | 117 ++++++++++++++++ .../{Button.test.jsx => Button.test.tsx} | 19 --- src/ui/Button/{Button.jsx => Button.tsx} | 60 +++++--- src/ui/Button/index.ts | 2 +- ...tories.jsx => ContextSwitcher.stories.tsx} | 37 ++--- ...ontextSwitcher.jsx => ContextSwitcher.tsx} | 131 ++++++++++-------- src/ui/ContextSwitcher/index.js | 1 - src/ui/ContextSwitcher/index.ts | 1 + ...go.stories.jsx => LoadingLogo.stories.tsx} | 15 +- .../{LoadingLogo.jsx => LoadingLogo.tsx} | 2 +- src/ui/LoadingLogo/{index.js => index.ts} | 0 src/ui/Modal/Modal.stories.tsx | 6 +- src/ui/Sidemenu/Sidemenu.stories.jsx | 37 ----- src/ui/Sidemenu/Sidemenu.stories.tsx | 42 ++++++ .../{Sidemenu.test.jsx => Sidemenu.test.tsx} | 2 +- .../Sidemenu/{Sidemenu.jsx => Sidemenu.tsx} | 14 +- .../TabNavigation/TabNavigation.stories.jsx | 31 ----- .../TabNavigation/TabNavigation.stories.tsx | 36 +++++ ...gation.test.jsx => TabNavigation.test.tsx} | 2 +- .../{TabNavigation.jsx => TabNavigation.tsx} | 15 +- 30 files changed, 374 insertions(+), 309 deletions(-) delete mode 100644 src/ui/Button/Button.stories.jsx create mode 100644 src/ui/Button/Button.stories.tsx rename src/ui/Button/{Button.test.jsx => Button.test.tsx} (78%) rename src/ui/Button/{Button.jsx => Button.tsx} (80%) rename src/ui/ContextSwitcher/{ContextSwitcher.stories.jsx => ContextSwitcher.stories.tsx} (55%) rename src/ui/ContextSwitcher/{ContextSwitcher.jsx => ContextSwitcher.tsx} (69%) delete mode 100644 src/ui/ContextSwitcher/index.js create mode 100644 src/ui/ContextSwitcher/index.ts rename src/ui/LoadingLogo/{LoadingLogo.stories.jsx => LoadingLogo.stories.tsx} (58%) rename src/ui/LoadingLogo/{LoadingLogo.jsx => LoadingLogo.tsx} (85%) rename src/ui/LoadingLogo/{index.js => index.ts} (100%) delete mode 100644 src/ui/Sidemenu/Sidemenu.stories.jsx create mode 100644 src/ui/Sidemenu/Sidemenu.stories.tsx rename src/ui/Sidemenu/{Sidemenu.test.jsx => Sidemenu.test.tsx} (95%) rename src/ui/Sidemenu/{Sidemenu.jsx => Sidemenu.tsx} (79%) delete mode 100644 src/ui/TabNavigation/TabNavigation.stories.jsx create mode 100644 src/ui/TabNavigation/TabNavigation.stories.tsx rename src/ui/TabNavigation/{TabNavigation.test.jsx => TabNavigation.test.tsx} (96%) rename src/ui/TabNavigation/{TabNavigation.jsx => TabNavigation.tsx} (76%) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index c40a5cf0d7..f6c9fc97dc 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,5 +1,6 @@ import { Preview } from '@storybook/react' import { themes } from '@storybook/theming' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import React from 'react' import Layout from './Layout' @@ -8,6 +9,8 @@ export const parameters = { actions: { argTypesRegex: '^on[A-Z].*' }, } +const queryClient = new QueryClient() + const localStorageResetDecorator = (Story) => { window.localStorage.clear() return @@ -16,9 +19,11 @@ const localStorageResetDecorator = (Story) => { export const decorators = [ localStorageResetDecorator, (Story) => ( - - - + + + + + ), ] diff --git a/src/pages/AccountSettings/tabs/Admin/ManageAdminCard/AdminTable/AdminTable.tsx b/src/pages/AccountSettings/tabs/Admin/ManageAdminCard/AdminTable/AdminTable.tsx index 434f70bf2e..99257fce74 100644 --- a/src/pages/AccountSettings/tabs/Admin/ManageAdminCard/AdminTable/AdminTable.tsx +++ b/src/pages/AccountSettings/tabs/Admin/ManageAdminCard/AdminTable/AdminTable.tsx @@ -124,7 +124,6 @@ export default function AdminTable() { email: user.email, revoke: ( <> - {/* @ts-expect-error */} - {/* @ts-expect-error */} , { wrapper: MemoryRouter }) - expect(mockError).toHaveBeenCalledTimes(1) - }) - }) - describe('when isLoading', () => { it('disables the button', () => { render( diff --git a/src/ui/Button/Button.jsx b/src/ui/Button/Button.tsx similarity index 80% rename from src/ui/Button/Button.jsx rename to src/ui/Button/Button.tsx index 09d2145067..8df8863c0e 100644 --- a/src/ui/Button/Button.jsx +++ b/src/ui/Button/Button.tsx @@ -1,5 +1,4 @@ import cs from 'classnames' -import PropTypes from 'prop-types' import AppLink from 'shared/AppLink' import Spinner from 'ui/Spinner' @@ -95,12 +94,46 @@ const loadingVariantClasses = { secondary: `disabled:text-white disabled:border-ds-pink-tertiary disabled:bg-ds-pink-default`, } -function pickVariant(variant, loading) { - const set = loading ? loadingVariantClasses : variantClasses +function pickVariant( + variant: keyof typeof variantClasses | keyof typeof loadingVariantClasses, + loading: boolean +) { + return loading + ? loadingVariantClasses[variant as keyof typeof loadingVariantClasses] + : variantClasses[variant as keyof typeof variantClasses] +} - return set[variant] +// using this type until AppLink is converted to TypeScript +export interface AppLinkProps { + pageName: string + text?: string + options?: Object + activeClassName?: string + showExternalIcon?: boolean + type?: 'submit' | 'button' | 'reset' + children?: React.ReactNode + exact?: boolean } +interface WithTo { + to: AppLinkProps + hook?: string +} +interface WithoutTo { + to?: never + hook: string +} + +interface ButtonProps extends React.HTMLProps { + variant?: keyof typeof variantClasses + isLoading?: boolean + disabled?: boolean +} + +type ExtendedButtonProps = ButtonProps & + (WithTo | WithoutTo) & + Partial + function Button({ to, variant = 'default', @@ -109,7 +142,7 @@ function Button({ hook, children, ...props -}) { +}: ExtendedButtonProps) { const className = cs( baseClass, { [baseDisabledClasses]: !isLoading }, @@ -151,21 +184,4 @@ function Button({ ) } -Button.propTypes = { - to: PropTypes.shape(AppLink.propTypes), - variant: PropTypes.oneOf(Object.keys(variantClasses)), - isLoading: PropTypes.bool, - disabled: PropTypes.bool, - hook: function (props, propName) { - if ( - props['to'] === undefined && - (props[propName] === undefined || typeof props[propName] != 'string') - ) { - return new Error( - 'If not using prop "to" you must provide prop "hook" of type string.' - ) - } - }, -} - export default Button diff --git a/src/ui/Button/index.ts b/src/ui/Button/index.ts index 3389ecb836..509273c34c 100644 --- a/src/ui/Button/index.ts +++ b/src/ui/Button/index.ts @@ -1 +1 @@ -export { default } from './Button' +export { default, type AppLinkProps } from './Button' diff --git a/src/ui/ContextSwitcher/ContextSwitcher.stories.jsx b/src/ui/ContextSwitcher/ContextSwitcher.stories.tsx similarity index 55% rename from src/ui/ContextSwitcher/ContextSwitcher.stories.jsx rename to src/ui/ContextSwitcher/ContextSwitcher.stories.tsx index 5e732366a0..1b591abc62 100644 --- a/src/ui/ContextSwitcher/ContextSwitcher.stories.jsx +++ b/src/ui/ContextSwitcher/ContextSwitcher.stories.tsx @@ -1,14 +1,16 @@ +import { type Meta, type StoryObj } from '@storybook/react' import { MemoryRouter, Route } from 'react-router-dom' import ContextSwitcher from './ContextSwitcher' -const Template = (args) => ( - - - - - -) +const meta: Meta = { + title: 'Components/ContextSwitcher', + component: ContextSwitcher, +} + +export default meta + +type Story = StoryObj const contexts = [ { @@ -34,13 +36,16 @@ const contexts = [ }, ] -export const SimpleContextSwitcher = Template.bind({}) -SimpleContextSwitcher.args = { - activeContext: 'dorianamouroux', - contexts, -} - -export default { - title: 'Components/ContextSwitcher', - component: ContextSwitcher, +export const SimpleContextSwitcher: Story = { + args: { + activeContext: { avatarUrl: '', username: 'dorianamouroux' }, + contexts, + }, + render: (args) => ( + + + + + + ), } diff --git a/src/ui/ContextSwitcher/ContextSwitcher.jsx b/src/ui/ContextSwitcher/ContextSwitcher.tsx similarity index 69% rename from src/ui/ContextSwitcher/ContextSwitcher.jsx rename to src/ui/ContextSwitcher/ContextSwitcher.tsx index ab6cd54152..2f5f5b4349 100644 --- a/src/ui/ContextSwitcher/ContextSwitcher.jsx +++ b/src/ui/ContextSwitcher/ContextSwitcher.tsx @@ -1,11 +1,11 @@ import cs from 'classnames' -import PropTypes from 'prop-types' import { useEffect, useRef, useState } from 'react' import { useParams } from 'react-router-dom' import { useIntersection } from 'react-use' import useClickAway from 'react-use/lib/useClickAway' import { useUpdateDefaultOrganization } from 'services/defaultOrganization' +import { Provider } from 'shared/api/helpers' import { providerToName } from 'shared/utils/provider' import A from 'ui/A' import Avatar from 'ui/Avatar' @@ -13,7 +13,15 @@ import Button from 'ui/Button' import Icon from 'ui/Icon' import Spinner from 'ui/Spinner' -function LoadMoreTrigger({ intersectionRef, onLoadMore }) { +interface LoadMoreTriggerProps { + onLoadMore?: () => void + intersectionRef: React.Ref +} + +function LoadMoreTrigger({ + intersectionRef, + onLoadMore, +}: LoadMoreTriggerProps) { if (!onLoadMore) { return null } @@ -28,12 +36,22 @@ function LoadMoreTrigger({ intersectionRef, onLoadMore }) { ) } -LoadMoreTrigger.propTypes = { - onLoadMore: PropTypes.func, - intersectionRef: PropTypes.shape({ current: PropTypes.instanceOf(Element) }), +interface ContextItemProps { + context: { + owner: { username: string | null } | null + pageName: string + } + defaultOrgUsername: string | null + setToggle: (arg: boolean) => void + owner?: string } -function ContextItem({ context, defaultOrgUsername, setToggle, owner }) { +function ContextItem({ + context, + defaultOrgUsername, + setToggle, + owner, +}: ContextItemProps) { const { owner: contextOwner, pageName } = context const orgUsername = contextOwner?.username const { mutate } = useUpdateDefaultOrganization() @@ -44,13 +62,15 @@ function ContextItem({ context, defaultOrgUsername, setToggle, owner }) { id="listbox-option-0" >
    {isGh ? (
  • - - + + Install Codecov GitHub app
  • @@ -184,7 +226,6 @@ function ContextSwitcher({ defaultOrgUsername={defaultOrgUsername} context={context} key={context?.owner?.username} - currentContext={activeContext} setToggle={setToggle} owner={activeContext?.username} /> @@ -199,26 +240,4 @@ function ContextSwitcher({ ) } -ContextSwitcher.propTypes = { - buttonVariant: PropTypes.oneOf(['default', 'outlined']), - contexts: PropTypes.arrayOf( - PropTypes.shape({ - owner: PropTypes.shape({ - avatarUrl: PropTypes.string.isRequired, - username: PropTypes.string, - }), - pageName: PropTypes.string.isRequired, - }) - ).isRequired, - currentUser: PropTypes.shape({ - defaultOrgUsername: PropTypes.string, - }), - activeContext: PropTypes.shape({ - avatarUrl: PropTypes.string.isRequired, - username: PropTypes.string.isRequired, - }), - onLoadMore: PropTypes.func, - isLoading: PropTypes.bool, -} - export default ContextSwitcher diff --git a/src/ui/ContextSwitcher/index.js b/src/ui/ContextSwitcher/index.js deleted file mode 100644 index 634b5a8a03..0000000000 --- a/src/ui/ContextSwitcher/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ContextSwitcher' diff --git a/src/ui/ContextSwitcher/index.ts b/src/ui/ContextSwitcher/index.ts new file mode 100644 index 0000000000..365f1c66cd --- /dev/null +++ b/src/ui/ContextSwitcher/index.ts @@ -0,0 +1 @@ +export { default, type Props } from './ContextSwitcher' diff --git a/src/ui/LoadingLogo/LoadingLogo.stories.jsx b/src/ui/LoadingLogo/LoadingLogo.stories.tsx similarity index 58% rename from src/ui/LoadingLogo/LoadingLogo.stories.jsx rename to src/ui/LoadingLogo/LoadingLogo.stories.tsx index 0f9ca7024b..a30ed49f7f 100644 --- a/src/ui/LoadingLogo/LoadingLogo.stories.jsx +++ b/src/ui/LoadingLogo/LoadingLogo.stories.tsx @@ -1,12 +1,17 @@ -import LoadingLogo from './LoadingLogo' - -const Template = (args) => +import { type Meta, type StoryObj } from '@storybook/react' -export const NormalLoadingLogo = Template.bind({}) +import LoadingLogo from './LoadingLogo' -export default { +const meta: Meta = { title: 'Components/LoadingLogo', component: LoadingLogo, +} + +export default meta + +type Story = StoryObj + +export const NormalLoadingLogo: Story = { parameters: { backgrounds: { default: 'dark', diff --git a/src/ui/LoadingLogo/LoadingLogo.jsx b/src/ui/LoadingLogo/LoadingLogo.tsx similarity index 85% rename from src/ui/LoadingLogo/LoadingLogo.jsx rename to src/ui/LoadingLogo/LoadingLogo.tsx index d36d838e8e..2ac7f1b2dc 100644 --- a/src/ui/LoadingLogo/LoadingLogo.jsx +++ b/src/ui/LoadingLogo/LoadingLogo.tsx @@ -3,7 +3,7 @@ import Logo from 'assets/codecov_logo.png' export default function LoadingLogo() { return ( - + Codecov Logo diff --git a/src/ui/LoadingLogo/index.js b/src/ui/LoadingLogo/index.ts similarity index 100% rename from src/ui/LoadingLogo/index.js rename to src/ui/LoadingLogo/index.ts diff --git a/src/ui/Modal/Modal.stories.tsx b/src/ui/Modal/Modal.stories.tsx index 8afb87e312..d1e511fec3 100644 --- a/src/ui/Modal/Modal.stories.tsx +++ b/src/ui/Modal/Modal.stories.tsx @@ -23,7 +23,7 @@ const RenderTemplate: React.FC<{ args: ModalProps }> = ({ args }) => { ), @@ -87,7 +87,7 @@ export const BaseModalOnly: React.FC = () => ( } footer={ - } diff --git a/src/ui/Sidemenu/Sidemenu.stories.jsx b/src/ui/Sidemenu/Sidemenu.stories.jsx deleted file mode 100644 index ea37a14953..0000000000 --- a/src/ui/Sidemenu/Sidemenu.stories.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import { MemoryRouter, Route, Switch } from 'react-router-dom' - -import Sidemenu from './Sidemenu' - -const Template = (args) => ( - - - - - - - - - - - - - - - - -) - -export const SimpleSidemenu = Template.bind({}) -SimpleSidemenu.args = { - links: [ - { pageName: 'accountAdmin', exact: true }, - { pageName: 'billingAndUsers' }, - { pageName: 'internalAccessTab' }, - { pageName: 'yamlTab', children: 'Global Yaml' }, - ], -} - -export default { - title: 'Components/Sidemenu', - component: Sidemenu, -} diff --git a/src/ui/Sidemenu/Sidemenu.stories.tsx b/src/ui/Sidemenu/Sidemenu.stories.tsx new file mode 100644 index 0000000000..4c53ab2615 --- /dev/null +++ b/src/ui/Sidemenu/Sidemenu.stories.tsx @@ -0,0 +1,42 @@ +import { type Meta, type StoryObj } from '@storybook/react' +import { MemoryRouter, Route, Switch } from 'react-router-dom' + +import Sidemenu from './Sidemenu' + +const meta: Meta = { + title: 'Components/Sidemenu', + component: Sidemenu, +} + +export default meta + +type Story = StoryObj + +export const SimpleSidemenu: Story = { + args: { + links: [ + { pageName: 'accountAdmin' }, + { pageName: 'billingAndUsers' }, + { pageName: 'internalAccessTab' }, + { pageName: 'yamlTab', children: 'Global Yaml' }, + ], + }, + render: (args) => ( + + + + + + + + + + + + + + + + + ), +} diff --git a/src/ui/Sidemenu/Sidemenu.test.jsx b/src/ui/Sidemenu/Sidemenu.test.tsx similarity index 95% rename from src/ui/Sidemenu/Sidemenu.test.jsx rename to src/ui/Sidemenu/Sidemenu.test.tsx index 99c49a84dd..8b8673f2c9 100644 --- a/src/ui/Sidemenu/Sidemenu.test.jsx +++ b/src/ui/Sidemenu/Sidemenu.test.tsx @@ -3,7 +3,7 @@ import { MemoryRouter, Route, Switch } from 'react-router-dom' import Sidemenu from './Sidemenu' -const wrapper = ({ children }) => { +const wrapper = ({ children }: { children: React.ReactNode }) => { return ( diff --git a/src/ui/Sidemenu/Sidemenu.jsx b/src/ui/Sidemenu/Sidemenu.tsx similarity index 79% rename from src/ui/Sidemenu/Sidemenu.jsx rename to src/ui/Sidemenu/Sidemenu.tsx index 64850de88d..ca30db0762 100644 --- a/src/ui/Sidemenu/Sidemenu.jsx +++ b/src/ui/Sidemenu/Sidemenu.tsx @@ -1,8 +1,11 @@ -import PropTypes from 'prop-types' - import AppLink from 'shared/AppLink' +import { type AppLinkProps } from 'ui/Button' + +interface SidemenuProps { + links: AppLinkProps[] +} -function Sidemenu({ links }) { +function Sidemenu({ links }: SidemenuProps) { return (