diff options
257 files changed, 1397 insertions, 9698 deletions
diff --git a/server/sonar-web/.eslintrc b/server/sonar-web/.eslintrc index 7a2728eb58a..3634bec594b 100644 --- a/server/sonar-web/.eslintrc +++ b/server/sonar-web/.eslintrc @@ -17,7 +17,7 @@ "local-rules/use-metrictype-enum": "warn", "local-rules/use-visibility-enum": "warn", "local-rules/convert-class-to-function-component": "warn", - "local-rules/no-conditional-rendering-of-deferredspinner": "warn", + "local-rules/no-conditional-rendering-of-spinner": "warn", "local-rules/use-jest-mocked": "warn", "local-rules/use-await-expect-async-matcher": "warn", "local-rules/no-implicit-coercion": "warn", diff --git a/server/sonar-web/config/indexHtmlTemplate.js b/server/sonar-web/config/indexHtmlTemplate.js index f1980952c1b..6d708a8c2e9 100644 --- a/server/sonar-web/config/indexHtmlTemplate.js +++ b/server/sonar-web/config/indexHtmlTemplate.js @@ -46,7 +46,7 @@ module.exports = (cssHash, jsHash) => ` <body> <div id="content" data-base-url="%WEB_CONTEXT%" data-server-status="%SERVER_STATUS%" data-instance="%INSTANCE%" data-official="%OFFICIAL%"> <div class="global-loading"> - <i class="spinner global-loading-spinner"></i> + <i class="global-loading-spinner"></i> <span aria-live="polite" class="global-loading-text">Loading...</span> </div> </div> diff --git a/server/sonar-web/design-system/.eslintrc b/server/sonar-web/design-system/.eslintrc index 07e825a3518..b6218abddcd 100644 --- a/server/sonar-web/design-system/.eslintrc +++ b/server/sonar-web/design-system/.eslintrc @@ -22,7 +22,7 @@ "local-rules/use-metrictype-enum": "warn", "local-rules/use-visibility-enum": "warn", "local-rules/convert-class-to-function-component": "warn", - "local-rules/no-conditional-rendering-of-deferredspinner": "warn", + "local-rules/no-conditional-rendering-of-spinner": "warn", "local-rules/use-jest-mocked": "warn", // New rules added after updating eslint packages to more recent versions than eslint-config-sonarqube diff --git a/server/sonar-web/design-system/src/components/Banner.tsx b/server/sonar-web/design-system/src/components/Banner.tsx index 4da28d16d1e..f7053a03924 100644 --- a/server/sonar-web/design-system/src/components/Banner.tsx +++ b/server/sonar-web/design-system/src/components/Banner.tsx @@ -30,6 +30,7 @@ export type Variant = 'error' | 'warning' | 'success' | 'info'; interface Props { children: ReactNode; + className?: string; onDismiss?: VoidFunction; variant: Variant; } @@ -61,19 +62,19 @@ function getVariantInfo(variant: Variant) { return variantList[variant]; } -export function Banner({ children, onDismiss, variant }: Props) { +export function Banner({ children, className, onDismiss, variant }: Props) { const variantInfo = getVariantInfo(variant); const intl = useIntl(); return ( - <div role="alert" style={{ height: LAYOUT_BANNER_HEIGHT }}> + <div className={className} role="alert" style={{ height: LAYOUT_BANNER_HEIGHT }}> <BannerWrapper backGroundColor={variantInfo.backGroundColor} fontColor={variantInfo.fontColor} > <BannerInner> - <div className="sw-flex"> + <div className="sw-flex sw-items-center"> <div className="sw-mr-3">{variantInfo.icon}</div> {children} </div> @@ -103,7 +104,7 @@ const BannerWrapper = styled.div<{ height: inherit; background-color: ${({ backGroundColor }) => themeColor(backGroundColor)}; color: ${({ fontColor }) => themeColor(fontColor)}; - ${tw`sw-z-global-navbar sw-fixed sw-w-full`} + ${tw`sw-z-popup sw-fixed sw-w-full`} ${tw`sw-sticky sw-top-0`} `; diff --git a/server/sonar-web/design-system/src/components/Spinner.tsx b/server/sonar-web/design-system/src/components/Spinner.tsx index a0ecc957962..d76bbfef775 100644 --- a/server/sonar-web/design-system/src/components/Spinner.tsx +++ b/server/sonar-web/design-system/src/components/Spinner.tsx @@ -49,16 +49,25 @@ export function Spinner(props: React.PropsWithChildren<Props>) { } return ( - <> + // Below: using <></> won't work in extenstions ('React' is not defined). This is because the + // name 'React' would already have been minified to something else when <> is resolved to + // React.Fragment + // eslint-disable-next-line react/jsx-fragments + <React.Fragment> <div className="sw-relative"> - <div className={classNames('sw-overflow-hidden', { 'sw-sr-only': !loading })}> + <div + className={classNames('sw-overflow-hidden', { + 'sw-sr-only': !loading, + it__loading: loading, + })} + > <StyledSpinner aria-live="polite" className={className} role="status"> {loading && <span className="sw-sr-only">{ariaLabel}</span>} </StyledSpinner> </div> </div> {!loading && (children ?? (placeholder && <Placeholder className={className} />) ?? null)} - </> + </React.Fragment> ); } diff --git a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CodeSnippet-test.tsx.snap b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CodeSnippet-test.tsx.snap index 6d2f58112e9..b279c96b379 100644 --- a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CodeSnippet-test.tsx.snap +++ b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CodeSnippet-test.tsx.snap @@ -91,12 +91,6 @@ exports[`should highlight code content correctly 1`] = ` pointer-events: none; } -.emotion-4:hover, -.emotion-4:active, -.emotion-4:focus { - border-color: rgb(197,205,223); -} - .code-snippet-highlighted-oneline .emotion-4 { bottom: 0.5rem; } @@ -303,12 +297,6 @@ exports[`should show full size when multiline with no editing 1`] = ` pointer-events: none; } -.emotion-4:hover, -.emotion-4:active, -.emotion-4:focus { - border-color: rgb(197,205,223); -} - .code-snippet-highlighted-oneline .emotion-4 { bottom: 0.5rem; } @@ -519,12 +507,6 @@ exports[`should show reduced size when single line with no editing 1`] = ` pointer-events: none; } -.emotion-4:hover, -.emotion-4:active, -.emotion-4:focus { - border-color: rgb(197,205,223); -} - .code-snippet-highlighted-oneline .emotion-4 { bottom: 0.5rem; } diff --git a/server/sonar-web/design-system/src/components/buttons/ButtonSecondary.tsx b/server/sonar-web/design-system/src/components/buttons/ButtonSecondary.tsx index fafaa4259cc..bd898e1e4ed 100644 --- a/server/sonar-web/design-system/src/components/buttons/ButtonSecondary.tsx +++ b/server/sonar-web/design-system/src/components/buttons/ButtonSecondary.tsx @@ -27,10 +27,4 @@ export const ButtonSecondary: React.FC<React.PropsWithChildren<ButtonProps>> = s --color: ${themeContrast('buttonSecondary')}; --focus: ${themeColor('buttonSecondaryBorder', OPACITY_20_PERCENT)}; --border: ${themeBorder('default', 'buttonSecondaryBorder')}; - - &:hover, - &:active, - &:focus { - border-color: ${themeColor('buttonSecondaryBorder')}; - } `; diff --git a/server/sonar-web/design-system/src/components/buttons/DangerButtonSecondary.tsx b/server/sonar-web/design-system/src/components/buttons/DangerButtonSecondary.tsx index c3cb55e3e8d..94935bf0bf0 100644 --- a/server/sonar-web/design-system/src/components/buttons/DangerButtonSecondary.tsx +++ b/server/sonar-web/design-system/src/components/buttons/DangerButtonSecondary.tsx @@ -27,10 +27,4 @@ export const DangerButtonSecondary: React.FC<React.PropsWithChildren<ButtonProps --color: ${themeContrast('dangerButtonSecondary')}; --focus: ${themeColor('dangerButtonSecondaryFocus', OPACITY_20_PERCENT)}; --border: ${themeBorder('default', 'dangerButtonSecondaryBorder')}; - - &:hover, - &:active, - &:focus { - border-color: ${themeColor('dangerButtonSecondaryBorder')}; - } `; diff --git a/server/sonar-web/design-system/src/components/input/DatePicker.tsx b/server/sonar-web/design-system/src/components/input/DatePicker.tsx index e282d1e4eed..89d4c415549 100644 --- a/server/sonar-web/design-system/src/components/input/DatePicker.tsx +++ b/server/sonar-web/design-system/src/components/input/DatePicker.tsx @@ -236,13 +236,11 @@ const StyledInteractiveIcon = styled(InteractiveIcon)` `; const StyledInputField = styled(InputField)` - input[type='text']& { - ${tw`sw-pl-8`}; - ${tw`sw-cursor-pointer`}; + ${tw`sw-pl-8`}; + ${tw`sw-cursor-pointer`}; - &.is-filled { - ${tw`sw-pr-8`}; - } + &.is-filled { + ${tw`sw-pr-8`}; } `; diff --git a/server/sonar-web/design-system/src/components/input/InputField.tsx b/server/sonar-web/design-system/src/components/input/InputField.tsx index 2d37c9b095f..3690c438216 100644 --- a/server/sonar-web/design-system/src/components/input/InputField.tsx +++ b/server/sonar-web/design-system/src/components/input/InputField.tsx @@ -129,14 +129,9 @@ const baseStyle = (props: ThemedProps) => css` `; const StyledInput = styled.input` - input[type='text']&, - input[type='number']&, - input[type='password']&, - input[type='email']& { - ${getInputVariant} - ${baseStyle} - ${tw`sw-h-control`} - } + ${getInputVariant} + ${baseStyle} + ${tw`sw-h-control`} `; const StyledTextArea = styled.textarea` diff --git a/server/sonar-web/design-system/src/components/input/RadioButton.tsx b/server/sonar-web/design-system/src/components/input/RadioButton.tsx index 2797de1b7f5..7ee29e59f89 100644 --- a/server/sonar-web/design-system/src/components/input/RadioButton.tsx +++ b/server/sonar-web/design-system/src/components/input/RadioButton.tsx @@ -140,9 +140,9 @@ export const RadioButtonStyled = styled.input` to right, ${themeColor('radioDisabledBackground')}, ${themeColor('radioDisabledBackground')} - ) !important; - background-clip: content-box, padding-box !important; - border: ${themeBorder('default', 'radioDisabledBorder')} !important; + ); + background-clip: content-box, padding-box; + border: ${themeBorder('default', 'radioDisabledBorder')}; } } `; diff --git a/server/sonar-web/design-system/src/components/modal/Modal.tsx b/server/sonar-web/design-system/src/components/modal/Modal.tsx index 8ba03e93d02..d7654dbce00 100644 --- a/server/sonar-web/design-system/src/components/modal/Modal.tsx +++ b/server/sonar-web/design-system/src/components/modal/Modal.tsx @@ -92,7 +92,7 @@ export function Modal({ <ReactModal aria={{ labelledby: 'modal_header_title' }} - className={classNames('design-system-modal-contents modal', { large: isLarge })} + className={classNames('design-system-modal-contents', { large: isLarge })} isOpen={isOpen} onRequestClose={onClose} overlayClassName="design-system-modal-overlay" @@ -148,8 +148,6 @@ const globalStyles = ({ theme }: { theme: Theme }) => css` &.large { max-width: 1280px; min-width: 1040px; - transform: translateX(-50%); - margin-left: 0px; } } diff --git a/server/sonar-web/eslint-local-rules/__tests__/no-conditional-rendering-of-deferredspinner-test.js b/server/sonar-web/eslint-local-rules/__tests__/no-conditional-rendering-of-spinner-test.js index 99a34407909..bcd456e9f98 100644 --- a/server/sonar-web/eslint-local-rules/__tests__/no-conditional-rendering-of-deferredspinner-test.js +++ b/server/sonar-web/eslint-local-rules/__tests__/no-conditional-rendering-of-spinner-test.js @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ const { RuleTester } = require('eslint'); -const noConditionalRenderingOfDeferredSpinner = require('../no-conditional-rendering-of-deferredspinner'); +const noConditionalRenderingOfSpinner = require('../no-conditional-rendering-of-spinner'); const ruleTester = new RuleTester({ parser: require.resolve('@typescript-eslint/parser'), @@ -29,44 +29,40 @@ const ruleTester = new RuleTester({ }, }); -ruleTester.run( - 'no-conditional-rendering-of-deferredspinner', - noConditionalRenderingOfDeferredSpinner, - { - valid: [ - { - code: `function MyCompontent({ loading }) { +ruleTester.run('no-conditional-rendering-of-spinner', noConditionalRenderingOfSpinner, { + valid: [ + { + code: `function MyCompontent({ loading }) { return <> <Spinner loading={loading} /> </> }`, - }, - ], - invalid: [ - { - code: `function MyCompontent({ loading }) { + }, + ], + invalid: [ + { + code: `function MyCompontent({ loading }) { return <> {loading && <Spinner />} </> }`, - errors: [{ messageId: 'noConditionalRenderingOfDeferredSpinner' }], - }, - { - code: `function MyComponent({ loading }) { + errors: [{ messageId: 'noConditionalRenderingOfSpinner' }], + }, + { + code: `function MyComponent({ loading }) { return <> {loading ? <Spinner /> : <div />} </> }`, - errors: [{ messageId: 'noConditionalRenderingOfDeferredSpinner' }], - }, - { - code: `function MyCompontent({ loaded }) { + errors: [{ messageId: 'noConditionalRenderingOfSpinner' }], + }, + { + code: `function MyCompontent({ loaded }) { return <> {loaded ? <div /> : <Spinner />} </> }`, - errors: [{ messageId: 'noConditionalRenderingOfDeferredSpinner' }], - }, - ], - } -); + errors: [{ messageId: 'noConditionalRenderingOfSpinner' }], + }, + ], +}); diff --git a/server/sonar-web/eslint-local-rules/index.js b/server/sonar-web/eslint-local-rules/index.js index 7d9345d3021..e80671fb5a5 100644 --- a/server/sonar-web/eslint-local-rules/index.js +++ b/server/sonar-web/eslint-local-rules/index.js @@ -20,7 +20,7 @@ module.exports = { 'use-jest-mocked': require('./use-jest-mocked'), 'convert-class-to-function-component': require('./convert-class-to-function-component'), - 'no-conditional-rendering-of-deferredspinner': require('./no-conditional-rendering-of-deferredspinner'), + 'no-conditional-rendering-of-spinner': require('./no-conditional-rendering-of-spinner'), 'use-visibility-enum': require('./use-visibility-enum'), 'use-componentqualifier-enum': require('./use-componentqualifier-enum'), 'use-metrickey-enum': require('./use-metrickey-enum'), diff --git a/server/sonar-web/eslint-local-rules/no-conditional-rendering-of-deferredspinner.js b/server/sonar-web/eslint-local-rules/no-conditional-rendering-of-spinner.js index 89540719d75..06ed0a9fa55 100644 --- a/server/sonar-web/eslint-local-rules/no-conditional-rendering-of-deferredspinner.js +++ b/server/sonar-web/eslint-local-rules/no-conditional-rendering-of-spinner.js @@ -20,7 +20,7 @@ module.exports = { meta: { messages: { - noConditionalRenderingOfDeferredSpinner: + noConditionalRenderingOfSpinner: 'For accessibility reasons, you should not conditionally render a <Spinner />. Always render it, and pass a loading prop instead.', }, }, @@ -30,18 +30,18 @@ module.exports = { switch (node.expression.type) { case 'LogicalExpression': const { right } = node.expression; - if (isDeferredSpinnerComponent(right)) { - context.report({ node, messageId: 'noConditionalRenderingOfDeferredSpinner' }); + if (isSpinnerComponent(right)) { + context.report({ node, messageId: 'noConditionalRenderingOfSpinner' }); } break; case 'ConditionalExpression': const { consequent, alternate } = node.expression; - if (isDeferredSpinnerComponent(consequent)) { - context.report({ node, messageId: 'noConditionalRenderingOfDeferredSpinner' }); + if (isSpinnerComponent(consequent)) { + context.report({ node, messageId: 'noConditionalRenderingOfSpinner' }); } - if (isDeferredSpinnerComponent(alternate)) { - context.report({ node, messageId: 'noConditionalRenderingOfDeferredSpinner' }); + if (isSpinnerComponent(alternate)) { + context.report({ node, messageId: 'noConditionalRenderingOfSpinner' }); } break; } @@ -50,7 +50,7 @@ module.exports = { }, }; -function isDeferredSpinnerComponent(element) { +function isSpinnerComponent(element) { return ( element.type === 'JSXElement' && element.openingElement && diff --git a/server/sonar-web/src/main/js/app/components/AlmSynchronisationWarning.tsx b/server/sonar-web/src/main/js/app/components/AlmSynchronisationWarning.tsx index 9f6259e4a32..ffc9f55bdaf 100644 --- a/server/sonar-web/src/main/js/app/components/AlmSynchronisationWarning.tsx +++ b/server/sonar-web/src/main/js/app/components/AlmSynchronisationWarning.tsx @@ -25,7 +25,6 @@ import { FormattedMessage } from 'react-intl'; import { translate, translateWithParameters } from '../../helpers/l10n'; import { AlmSyncStatus } from '../../types/provisioning'; import { TaskStatuses } from '../../types/tasks'; -import './SystemAnnouncement.css'; interface SynchronisationWarningProps { short?: boolean; diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx index aed1b98b8f6..b96e54a5d4d 100644 --- a/server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx +++ b/server/sonar-web/src/main/js/app/components/ComponentContainerNotFound.tsx @@ -17,9 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Card, CenteredLayout, Link, SubHeading } from 'design-system/lib'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; -import Link from '../../components/common/Link'; import { translate } from '../../helpers/l10n'; export interface ComponentContainerNotFoundProps { @@ -32,19 +32,15 @@ export default function ComponentContainerNotFound({ const componentType = isPortfolioLike ? 'portfolio' : 'project'; return ( - <> + <CenteredLayout className="sw-flex sw-justify-around" id="bd"> <Helmet defaultTitle={translate('404_not_found')} defer={false} /> - <div className="page-wrapper-simple" id="bd"> - <div className="page-simple" id="nonav"> - <h2 className="big-spacer-bottom"> - {translate('dashboard', componentType, 'not_found')} - </h2> - <p className="spacer-bottom">{translate('dashboard', componentType, 'not_found.2')}</p> - <p> - <Link to="/">{translate('go_back_to_homepage')}</Link> - </p> - </div> - </div> - </> + <Card className="sw-mt-24" id="nonav"> + <SubHeading>{translate('dashboard', componentType, 'not_found')}</SubHeading> + <p className="sw-mb-2">{translate('dashboard', componentType, 'not_found.2')}</p> + <p> + <Link to="/">{translate('go_back_to_homepage')}</Link> + </p> + </Card> + </CenteredLayout> ); } diff --git a/server/sonar-web/src/main/js/app/components/DocumentationRedirect.tsx b/server/sonar-web/src/main/js/app/components/DocumentationRedirect.tsx index db9b1473502..ce07d94d59d 100644 --- a/server/sonar-web/src/main/js/app/components/DocumentationRedirect.tsx +++ b/server/sonar-web/src/main/js/app/components/DocumentationRedirect.tsx @@ -17,10 +17,10 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Link } from 'design-system'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; import { useLocation } from 'react-router-dom'; -import Link from '../../components/common/Link'; import { useDocUrl } from '../../helpers/docs'; const PAUSE_REDIRECT = 1; @@ -34,12 +34,12 @@ export default function DocumentationRedirect() { <Helmet> <meta httpEquiv="refresh" content={`${PAUSE_REDIRECT}; url='${url}'`} /> </Helmet> - <div className="global-loading"> - <div className="display-flex-center"> - <i className="spinner global-loading-spinner" /> - <span className="spacer-left global-loading-text">Redirecting...</span> + <div className="sw-flex sw-flex-col sw-items-center sw-gap-4 sw-h-[100vh]"> + <div className="global-loading"> + <i className="global-loading-spinner" /> + <span className="global-loading-text">Redirecting...</span> </div> - <div className="display-flex-justify-content spacer-top large"> + <div> <Link to={url}>Click here if you're not being redirected automatically</Link> </div> </div> diff --git a/server/sonar-web/src/main/js/app/components/GitHubSynchronisationWarning.tsx b/server/sonar-web/src/main/js/app/components/GitHubSynchronisationWarning.tsx index 5adbce4ac73..12c1592adb7 100644 --- a/server/sonar-web/src/main/js/app/components/GitHubSynchronisationWarning.tsx +++ b/server/sonar-web/src/main/js/app/components/GitHubSynchronisationWarning.tsx @@ -20,7 +20,6 @@ import * as React from 'react'; import { useGitHubSyncStatusQuery } from '../../queries/identity-provider/github'; import AlmSynchronisationWarning from './AlmSynchronisationWarning'; -import './SystemAnnouncement.css'; interface Props { short?: boolean; diff --git a/server/sonar-web/src/main/js/app/components/GitLabSynchronisationWarning.tsx b/server/sonar-web/src/main/js/app/components/GitLabSynchronisationWarning.tsx index 39a18f809ad..806882fa76b 100644 --- a/server/sonar-web/src/main/js/app/components/GitLabSynchronisationWarning.tsx +++ b/server/sonar-web/src/main/js/app/components/GitLabSynchronisationWarning.tsx @@ -20,7 +20,6 @@ import * as React from 'react'; import { useGitLabSyncStatusQuery } from '../../queries/identity-provider/gitlab'; import AlmSynchronisationWarning from './AlmSynchronisationWarning'; -import './SystemAnnouncement.css'; interface Props { short?: boolean; diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx index 66951e5aae3..d8aa49d5ce2 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx @@ -18,8 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { ThemeProvider } from '@emotion/react'; -import classNames from 'classnames'; -import { lightTheme, ToastMessageContainer } from 'design-system'; +import styled from '@emotion/styled'; +import { lightTheme, themeColor, ToastMessageContainer } from 'design-system'; import * as React from 'react'; import { Outlet, useLocation } from 'react-router-dom'; import A11yProvider from '../../components/a11y/A11yProvider'; @@ -38,30 +38,11 @@ import StartupModal from './StartupModal'; import SystemAnnouncement from './SystemAnnouncement'; import UpdateNotification from './update-notification/UpdateNotification'; -const TEMP_PAGELIST_WITH_NEW_BACKGROUND = [ - '/admin/extension/governance/views_console', - '/admin/extension/license', - '/code', - '/coding_rules', - '/component_measures', - '/dashboard', - '/portfolios', - '/profiles', - '/project/activity', - '/project/admin/extension/governance/console', - '/project/admin/extension/governance/report', - '/project/extension/securityreport/securityreport', - '/project/information', - '/project/issues', - '/projects', - '/quality_gates', - '/security_hotspots', - '/web_api_v2', - '/portfolio', - '/account', -]; - -const TEMP_PAGELIST_WITH_NEW_BACKGROUND_WHITE = [ +/* + * These pages need a white background (aka 'secondary', rather than the default 'primary') + * This should be revisited at some point (why the exception?) + */ +const PAGES_WITH_SECONDARY_BACKGROUND = [ '/tutorials', '/projects/create', '/project/baseline', @@ -101,15 +82,9 @@ export default function GlobalContainer() { <A11yProvider> <A11ySkipLinks /> <div className="global-container"> - <div - className={classNames('page-wrapper', { - 'new-background': TEMP_PAGELIST_WITH_NEW_BACKGROUND.some((element) => - location.pathname.startsWith(element), - ), - 'white-background': TEMP_PAGELIST_WITH_NEW_BACKGROUND_WHITE.includes( - location.pathname, - ), - })} + <GlobalBackground + secondary={PAGES_WITH_SECONDARY_BACKGROUND.includes(location.pathname)} + className="sw-box-border sw-flex-[1_0_auto]" id="container" > <div className="page-container"> @@ -136,7 +111,7 @@ export default function GlobalContainer() { </Workspace> </div> <PromotionNotification /> - </div> + </GlobalBackground> <GlobalFooter /> </div> <StartupModal /> @@ -145,3 +120,8 @@ export default function GlobalContainer() { </ThemeProvider> ); } + +const GlobalBackground = styled.div<{ secondary: boolean }>` + background-color: ${({ secondary }) => + themeColor(secondary ? 'backgroundSecondary' : 'backgroundPrimary')}; +`; diff --git a/server/sonar-web/src/main/js/app/components/LicensePromptModal.tsx b/server/sonar-web/src/main/js/app/components/LicensePromptModal.tsx index 3ed3a5f191e..cee66aca180 100644 --- a/server/sonar-web/src/main/js/app/components/LicensePromptModal.tsx +++ b/server/sonar-web/src/main/js/app/components/LicensePromptModal.tsx @@ -17,11 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +import { Modal } from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import Link from '../../components/common/Link'; -import Modal from '../../components/controls/Modal'; -import { ResetButtonLink } from '../../components/controls/buttons'; import { translate } from '../../helpers/l10n'; interface Props { @@ -29,13 +29,9 @@ interface Props { } export default function LicensePromptModal({ onClose }: Readonly<Props>) { - const header = translate('license.prompt.title'); return ( - <Modal contentLabel={header} onRequestClose={onClose}> - <header className="modal-head"> - <h2>{header}</h2> - </header> - <div className="modal-body"> + <Modal + body={ <FormattedMessage defaultMessage={translate('license.prompt.description')} id="license.prompt.description" @@ -47,10 +43,10 @@ export default function LicensePromptModal({ onClose }: Readonly<Props>) { ), }} /> - </div> - <footer className="modal-foot"> - <ResetButtonLink onClick={onClose}>{translate('cancel')}</ResetButtonLink> - </footer> - </Modal> + } + headerTitle={translate('license.prompt.title')} + onClose={onClose} + secondaryButtonLabel={translate('cancel')} + /> ); } diff --git a/server/sonar-web/src/main/js/app/components/NonAdminPagesContainer.tsx b/server/sonar-web/src/main/js/app/components/NonAdminPagesContainer.tsx index e7dad14f203..6053ebd3de9 100644 --- a/server/sonar-web/src/main/js/app/components/NonAdminPagesContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/NonAdminPagesContainer.tsx @@ -17,9 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { CenteredLayout, FlagMessage } from 'design-system'; import * as React from 'react'; import { Outlet } from 'react-router-dom'; -import { Alert } from '../../components/ui/Alert'; import { translate } from '../../helpers/l10n'; import { isApplication } from '../../types/component'; import { ComponentContext } from './componentContext/ComponentContext'; @@ -34,17 +34,18 @@ export default function NonAdminPagesContainer() { */ if (component && isApplication(component.qualifier) && !component.canBrowseAllChildProjects) { return ( - <div className="page page-limited display-flex-justify-center"> - <Alert - className="it__alert-no-access-all-child-project max-width-60 huge-spacer-top" - display="block" - variant="error" - > - <p>{translate('application.cannot_access_all_child_projects1')}</p> - <br /> - <p>{translate('application.cannot_access_all_child_projects2')}</p> - </Alert> - </div> + <CenteredLayout + className="sw-py-8 sw-body-md sw-flex sw-flex-col sw-items-center" + id="code-page" + > + <FlagMessage className="it__alert-no-access-all-child-project sw-mt-10" variant="error"> + <p> + {translate('application.cannot_access_all_child_projects1')} + <br /> + {translate('application.cannot_access_all_child_projects2')} + </p> + </FlagMessage> + </CenteredLayout> ); } diff --git a/server/sonar-web/src/main/js/app/components/PluginRiskConsent.css b/server/sonar-web/src/main/js/app/components/PluginRiskConsent.css deleted file mode 100644 index 142d8bafe5f..00000000000 --- a/server/sonar-web/src/main/js/app/components/PluginRiskConsent.css +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.plugin-risk-consent-page { - padding-top: 10vh; -} - -.plugin-risk-consent-page h1 { - line-height: 1.5; - font-size: 24px; - font-weight: 300; - text-align: center; -} - -.plugin-risk-consent-content { - min-width: 500px; - width: 40%; - margin: 0 auto; -} diff --git a/server/sonar-web/src/main/js/app/components/PluginRiskConsent.tsx b/server/sonar-web/src/main/js/app/components/PluginRiskConsent.tsx index a4f0e5a3a85..8ac5fff5109 100644 --- a/server/sonar-web/src/main/js/app/components/PluginRiskConsent.tsx +++ b/server/sonar-web/src/main/js/app/components/PluginRiskConsent.tsx @@ -17,10 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +import { ButtonPrimary, Card, Title } from 'design-system'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; import { setSimpleSettingValue } from '../../api/settings'; -import { Button } from '../../components/controls/buttons'; import { whenLoggedIn } from '../../components/hoc/whenLoggedIn'; import { Router, withRouter } from '../../components/hoc/withRouter'; import { translate } from '../../helpers/l10n'; @@ -30,15 +31,14 @@ import { Permissions } from '../../types/permissions'; import { RiskConsent } from '../../types/plugins'; import { SettingsKey } from '../../types/settings'; import { LoggedInUser } from '../../types/users'; -import './PluginRiskConsent.css'; export interface PluginRiskConsentProps { currentUser: LoggedInUser; router: Router; } -export function PluginRiskConsent(props: PluginRiskConsentProps) { - const { router, currentUser } = props; +export function PluginRiskConsent(props: Readonly<PluginRiskConsentProps>) { + const { currentUser, router } = props; const isAdmin = hasGlobalPermission(currentUser, Permissions.Admin); @@ -67,20 +67,24 @@ export function PluginRiskConsent(props: PluginRiskConsentProps) { }; return ( - <div className="plugin-risk-consent-page"> + <> <Helmet defer={false} title={translate('plugin_risk_consent.page')} /> - <div className="plugin-risk-consent-content boxed-group"> - <div className="boxed-group-inner text-center"> - <h1 className="big-spacer-bottom">{translate('plugin_risk_consent.title')}</h1> - <p className="big big-spacer-bottom">{translate('plugin_risk_consent.description')}</p> - <p className="big huge-spacer-bottom">{translate('plugin_risk_consent.description2')}</p> - <Button className="big-spacer-bottom" onClick={acknowledgeRisk}> - {translate('plugin_risk_consent.action')} - </Button> - </div> - </div> - </div> + <Card + className="sw-body-md sw-min-w-[500px] sw-mx-auto sw-mt-[10vh] sw-w-[40%] sw-text-center" + data-testid="plugin-risk-consent-page" + > + <Title className="sw-mb-4">{translate('plugin_risk_consent.title')}</Title> + + <p className="sw-mb-4">{translate('plugin_risk_consent.description')}</p> + + <p className="sw-mb-6">{translate('plugin_risk_consent.description2')}</p> + + <ButtonPrimary className="sw-my-4" onClick={acknowledgeRisk}> + {translate('plugin_risk_consent.action')} + </ButtonPrimary> + </Card> + </> ); } diff --git a/server/sonar-web/src/main/js/app/components/SonarLintConnection.css b/server/sonar-web/src/main/js/app/components/SonarLintConnection.css deleted file mode 100644 index 26bdc7cde12..00000000000 --- a/server/sonar-web/src/main/js/app/components/SonarLintConnection.css +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.sonarlint-connection-page { - padding-top: 10vh; -} - -.sonarlint-connection-page h1 { - line-height: 1.5; - font-size: 24px; - font-weight: 300; - text-align: center; -} - -.sonarlint-connection-content { - min-width: 600px; - width: 40%; - margin: 0 auto; -} - -.sonarlint-connection-page ol { - list-style: inside decimal; -} - -.sonarlint-connection-page .field-label { - display: inline-block; - width: 150px; - color: var(--secondFontColor); - text-align: start; - flex-shrink: 0; -} - -.sonarlint-connection-page .sonarlint-token-value { - background-color: var(--codeBackground); - border: 1px solid var(--barBorderColor); - padding: calc(var(--gridSize) / 2) var(--gridSize); -} diff --git a/server/sonar-web/src/main/js/app/components/SonarLintConnection.tsx b/server/sonar-web/src/main/js/app/components/SonarLintConnection.tsx index 6b8832dc414..4f8a19c4d40 100644 --- a/server/sonar-web/src/main/js/app/components/SonarLintConnection.tsx +++ b/server/sonar-web/src/main/js/app/components/SonarLintConnection.tsx @@ -17,19 +17,27 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { + ButtonPrimary, + Card, + CardSeparator, + CheckIcon, + ClipboardButton, + InputField, + Link, + ListItem, + Note, + OrderedList, + Title, +} from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { useSearchParams } from 'react-router-dom'; -import Link from '../../components/common/Link'; -import { Button } from '../../components/controls/buttons'; -import { ClipboardButton } from '../../components/controls/clipboard'; import { whenLoggedIn } from '../../components/hoc/whenLoggedIn'; -import CheckIcon from '../../components/icons/CheckIcon'; import { translate, translateWithParameters } from '../../helpers/l10n'; import { generateSonarLintUserToken, portIsValid, sendUserToken } from '../../helpers/sonarlint'; import { NewUserToken } from '../../types/token'; import { LoggedInUser } from '../../types/users'; -import './SonarLintConnection.css'; enum Status { request, @@ -76,127 +84,98 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) { }, [port, ideName, login]); return ( - <div className="sonarlint-connection-page"> - <div className="sonarlint-connection-content boxed-group"> - <div className="boxed-group-inner text-center"> - {status === Status.request && ( - <> - <h1 className="big-spacer-top big-spacer-bottom"> - {translate('sonarlint-connection.request.title')} - </h1> - <img - alt="" - aria-hidden - className="big-spacer-top big-spacer-bottom" - src="/images/SonarLint-connection-request.png" - /> - <p className="big big-spacer-top big-spacer-bottom"> - {translateWithParameters('sonarlint-connection.request.description', ideName)} - </p> - <p className="big huge-spacer-bottom"> - {translate('sonarlint-connection.request.description2')} - </p> - - <Button className="big-spacer-bottom" onClick={authorize}> - <CheckIcon className="spacer-right" /> - {translate('sonarlint-connection.request.action')} - </Button> - </> - )} - - {status === Status.tokenError && ( - <> - <img - alt="" - aria-hidden - className="big-spacer-top big-spacer-bottom padded-top" - src="/images/cross.svg" - /> - <h1 className="big-spacer-bottom"> - {translate('sonarlint-connection.token-error.title')} - </h1> - <p className="big big-spacer-top big-spacer-bottom"> - {translate('sonarlint-connection.token-error.description')} - </p> - <p className="big huge-spacer-bottom"> - <FormattedMessage - id="sonarlint-connection.token-error.description2" - defaultMessage={translate('sonarlint-connection.token-error.description2')} - values={{ - link: ( - <Link to="/account/security"> - {translate('sonarlint-connection.token-error.description2.link')} - </Link> - ), - }} - /> - </p> - </> - )} - - {status === Status.tokenCreated && newToken && ( - <> - <img - alt="" - aria-hidden - className="big-spacer-top big-spacer-bottom padded-top" - src="/images/check.svg" - /> - <h1 className="big-spacer-bottom"> - {translate('sonarlint-connection.connection-error.title')} - </h1> - <p className="big big-spacer-top big-spacer-bottom"> - {translate('sonarlint-connection.connection-error.description')} - </p> - <div className="display-flex-center"> - <span className="field-label"> - {translate('sonarlint-connection.connection-error.token-name')} - </span> - {newToken.name} - </div> - <hr /> - <div className="display-flex-center"> - <span className="field-label"> - {translate('sonarlint-connection.connection-error.token-value')} - </span> - <span className="sonarlint-token-value">{newToken.token}</span> - <ClipboardButton className="big-spacer-left" copyValue={newToken.token} /> - </div> - <div className="big huge-spacer-top"> - <strong>{translate('sonarlint-connection.connection-error.next-steps')}</strong> - </div> - <ol className="big big-spacer-top big-spacer-bottom"> - <li>{translate('sonarlint-connection.connection-error.step1')}</li> - <li>{translate('sonarlint-connection.connection-error.step2')}</li> - </ol> - </> - )} - - {status === Status.tokenSent && newToken && ( - <> - <h1 className="big-spacer-top big-spacer-bottom"> - {translate('sonarlint-connection.success.title')} - </h1> - <img - alt="" - aria-hidden - className="big-spacer-bottom" - src="/images/SonarLint-connection-ok.png" - /> - <p className="big big-spacer-top big-spacer-bottom"> - {translateWithParameters('sonarlint-connection.success.description', newToken.name)} - </p> - <div className="big huge-spacer-top"> - <strong>{translate('sonarlint-connection.success.last-step')}</strong> - </div> - <div className="big big-spacer-top big-spacer-bottom"> - {translate('sonarlint-connection.success.step')} - </div> - </> - )} - </div> - </div> - </div> + <Card className="sw-mt-[10vh] sw-mx-auto sw-w-[650px] sw-text-center"> + {status === Status.request && ( + <> + <Title>{translate('sonarlint-connection.request.title')}</Title> + <img + alt="" + className="sw-my-4" + role="presentation" + src="/images/SonarLint-connection-request.png" + /> + <p className="sw-my-4"> + {translateWithParameters('sonarlint-connection.request.description', ideName)} + </p> + <p className="sw-mb-10">{translate('sonarlint-connection.request.description2')}</p> + + <ButtonPrimary icon={<CheckIcon fill="currentColor" />} onClick={authorize}> + {translate('sonarlint-connection.request.action')} + </ButtonPrimary> + </> + )} + + {status === Status.tokenError && ( + <> + <img alt="" className="sw-my-4 sw-pt-2" role="presentation" src="/images/cross.svg" /> + <Title>{translate('sonarlint-connection.token-error.title')}</Title> + <p className="sw-my-4">{translate('sonarlint-connection.token-error.description')}</p> + <p className="sw-mb-4"> + <FormattedMessage + id="sonarlint-connection.token-error.description2" + defaultMessage={translate('sonarlint-connection.token-error.description2')} + values={{ + link: ( + <Link to="/account/security"> + {translate('sonarlint-connection.token-error.description2.link')} + </Link> + ), + }} + /> + </p> + </> + )} + + {status === Status.tokenCreated && newToken && ( + <> + <img alt="" className="sw-my-4 sw-pt-2" role="presentation" src="/images/check.svg" /> + <Title>{translate('sonarlint-connection.connection-error.title')}</Title> + <p className="sw-my-6"> + {translate('sonarlint-connection.connection-error.description')} + </p> + <div className="sw-flex sw-items-center"> + <Note className="sw-w-abs-150 sw-text-start"> + {translate('sonarlint-connection.connection-error.token-name')} + </Note> + {newToken.name} + </div> + <CardSeparator className="sw-my-3" /> + <div className="sw-flex sw-items-center"> + <Note className="sw-min-w-abs-150 sw-text-start"> + {translate('sonarlint-connection.connection-error.token-value')} + </Note> + <InputField className="sw-cursor-text" disabled size="full" value={newToken.token} /> + <ClipboardButton className="sw-ml-2" copyValue={newToken.token} /> + </div> + <div className="sw-mt-10"> + <strong>{translate('sonarlint-connection.connection-error.next-steps')}</strong> + </div> + <OrderedList className="sw-list-inside sw-mb-4"> + <ListItem>{translate('sonarlint-connection.connection-error.step1')}</ListItem> + <ListItem>{translate('sonarlint-connection.connection-error.step2')}</ListItem> + </OrderedList> + </> + )} + + {status === Status.tokenSent && newToken && ( + <> + <Title>{translate('sonarlint-connection.success.title')}</Title> + <img + alt="" + className="sw-mb-4" + role="presentation" + src="/images/SonarLint-connection-ok.png" + /> + <p className="sw-my-4"> + {translateWithParameters('sonarlint-connection.success.description', newToken.name)} + </p> + <div className="sw-mt-10"> + <strong>{translate('sonarlint-connection.success.last-step')}</strong> + </div> + <div className="sw-my-4">{translate('sonarlint-connection.success.step')}</div> + </> + )} + </Card> ); } diff --git a/server/sonar-web/src/main/js/app/components/SystemAnnouncement.css b/server/sonar-web/src/main/js/app/components/SystemAnnouncement.css deleted file mode 100644 index aec6caf4da1..00000000000 --- a/server/sonar-web/src/main/js/app/components/SystemAnnouncement.css +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.system-announcement-wrapper { - min-height: 34px; -} - -.system-announcement-banner { - box-sizing: border-box; - width: 100%; - z-index: var(--globalBannerZIndex); - margin-bottom: 0% !important; -} diff --git a/server/sonar-web/src/main/js/app/components/SystemAnnouncement.tsx b/server/sonar-web/src/main/js/app/components/SystemAnnouncement.tsx index c80e8c966ad..b64f10b404b 100644 --- a/server/sonar-web/src/main/js/app/components/SystemAnnouncement.tsx +++ b/server/sonar-web/src/main/js/app/components/SystemAnnouncement.tsx @@ -17,14 +17,13 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import classNames from 'classnames'; +import styled from '@emotion/styled'; +import { FlagWarningIcon, themeBorder, themeColor } from 'design-system'; import { keyBy, throttle } from 'lodash'; import * as React from 'react'; import { getValues } from '../../api/settings'; -import { Alert } from '../../components/ui/Alert'; import { Feature } from '../../types/features'; import { GlobalSettingKeys, SettingValue } from '../../types/settings'; -import './SystemAnnouncement.css'; import withAvailableFeatures, { WithAvailableFeaturesProps, } from './available-features/withAvailableFeatures'; @@ -78,20 +77,28 @@ export class SystemAnnouncement extends React.PureComponent<WithAvailableFeature const { displayMessage, message } = this.state; return ( - <div className={classNames({ 'system-announcement-wrapper': displayMessage && message })}> - <Alert - className="system-announcement-banner" - title={message} - display="banner" - variant="warning" - aria-live="assertive" - role="alert" - > - {displayMessage && message} - </Alert> - </div> + <StyledBanner + className="sw-py-3 sw-px-4 sw-gap-3" + style={!(displayMessage && message.length > 0) ? { display: 'none' } : {}} + title={message} + aria-live="assertive" + role="alert" + > + <FlagWarningIcon /> + <span>{displayMessage && message}</span> + </StyledBanner> ); } } export default withAvailableFeatures(SystemAnnouncement); + +const StyledBanner = styled.div` + display: flex; + align-items: center; + box-sizing: border-box; + width: 100%; + + background-color: ${themeColor('warningBackground')}; + border-bottom: ${themeBorder('default', 'warningBorder')}; +`; diff --git a/server/sonar-web/src/main/js/app/components/__tests__/NonAdminPagesContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/NonAdminPagesContainer-test.tsx index 20e7da86992..01f497a8d08 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/NonAdminPagesContainer-test.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/NonAdminPagesContainer-test.tsx @@ -23,8 +23,8 @@ import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { mockComponent } from '../../../helpers/mocks/component'; import { ComponentContextShape, ComponentQualifier } from '../../../types/component'; import { Component } from '../../../types/types'; -import { ComponentContext } from '../componentContext/ComponentContext'; import NonAdminPagesContainer from '../NonAdminPagesContainer'; +import { ComponentContext } from '../componentContext/ComponentContext'; function Child() { return <div>Test Child</div>; @@ -34,7 +34,7 @@ it('should render correctly for an user that does not have access to all childre renderNonAdminPagesContainer( mockComponent({ qualifier: ComponentQualifier.Application, canBrowseAllChildProjects: false }), ); - expect(screen.getByText('application.cannot_access_all_child_projects1')).toBeInTheDocument(); + expect(screen.getByText(/^application.cannot_access_all_child_projects1/)).toBeInTheDocument(); }); it('should render correctly', () => { diff --git a/server/sonar-web/src/main/js/app/components/__tests__/SonarLintConnection-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/SonarLintConnection-test.tsx index bf9552c0d2a..1914cdf185f 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/SonarLintConnection-test.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/SonarLintConnection-test.tsx @@ -112,7 +112,7 @@ it('should handle connection errors', async () => { ).toBeInTheDocument(); const tokenValue = tokenMock.getLastToken()?.token ?? ''; - expect(await screen.findByText(tokenValue)).toBeInTheDocument(); + expect(await screen.findByRole('textbox')).toHaveValue(tokenValue); }); it('should require authentication if user is not logged in', () => { diff --git a/server/sonar-web/src/main/js/app/components/extensions/Extension.tsx b/server/sonar-web/src/main/js/app/components/extensions/Extension.tsx index 3d036ba3d86..1b79c3fad1c 100644 --- a/server/sonar-web/src/main/js/app/components/extensions/Extension.tsx +++ b/server/sonar-web/src/main/js/app/components/extensions/Extension.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + import { withTheme } from '@emotion/react'; import { QueryClient } from '@tanstack/react-query'; import { Theme } from 'design-system'; @@ -35,30 +36,29 @@ import { AppState } from '../../../types/appstate'; import { ExtensionStartMethod } from '../../../types/extension'; import { Dict, Extension as TypeExtension } from '../../../types/types'; import { CurrentUser, HomePage } from '../../../types/users'; -import * as theme from '../../theme'; import withAppStateContext from '../app-state/withAppStateContext'; import withCurrentUserContext from '../current-user/withCurrentUserContext'; export interface ExtensionProps extends WrappedComponentProps { - theme: Theme; appState: AppState; currentUser: CurrentUser; extension: TypeExtension; location: Location; - options?: Dict<any>; - router: Router; + options?: Dict<unknown>; queryClient: QueryClient; + router: Router; + theme: Theme; updateCurrentUserHomepage: (homepage: HomePage) => void; } interface State { - extensionElement?: React.ReactElement<any>; + extensionElement?: React.ReactElement<unknown>; } class Extension extends React.PureComponent<ExtensionProps, State> { container?: HTMLElement | null; - stop?: Function; state: State = {}; + stop?: Function; componentDidMount() { this.startExtension(); @@ -78,23 +78,22 @@ class Extension extends React.PureComponent<ExtensionProps, State> { } handleStart = (start: ExtensionStartMethod) => { - const { theme: dsTheme, queryClient } = this.props; + const { theme, queryClient } = this.props; + const result = start({ appState: this.props.appState, - el: this.container, + baseUrl: getBaseUrl(), currentUser: this.props.currentUser, + el: this.container, intl: this.props.intl, + l10nBundle: getCurrentL10nBundle(), location: this.props.location, + queryClient, router: this.props.router, theme, - // New theme from design-system, we should drop old theme once the migration to miui is done - dsTheme, - baseUrl: getBaseUrl(), - l10nBundle: getCurrentL10nBundle(), // See SONAR-16207 and core-extension-enterprise-server/src/main/js/portfolios/components/Header.tsx // for more information on why we're passing this as a prop to an extension. updateCurrentUserHomepage: this.props.updateCurrentUserHomepage, - queryClient, ...this.props.options, }); diff --git a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.css b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.css deleted file mode 100644 index 187579b9109..00000000000 --- a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.css +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.indexation-notification-wrapper { - height: 34px; -} - -.indexation-notification-banner { - width: 100%; - z-index: var(--globalBannerZIndex); - margin-bottom: 0 !important; -} diff --git a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx index bfbeafe96eb..665c9f333e9 100644 --- a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx +++ b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotification.tsx @@ -26,7 +26,6 @@ import { IndexationNotificationType } from '../../../types/indexation'; import { Permissions } from '../../../types/permissions'; import { CurrentUser, isLoggedIn } from '../../../types/users'; import withCurrentUserContext from '../current-user/withCurrentUserContext'; -import './IndexationNotification.css'; import IndexationNotificationHelper from './IndexationNotificationHelper'; import IndexationNotificationRenderer from './IndexationNotificationRenderer'; diff --git a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx index bc46d8afb4d..5bb714c9697 100644 --- a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx +++ b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx @@ -19,12 +19,20 @@ */ /* eslint-disable react/no-unused-prop-types */ -import classNames from 'classnames'; +import styled from '@emotion/styled'; +import { + FlagErrorIcon, + FlagSuccessIcon, + FlagWarningIcon, + Link, + Spinner, + ThemeColors, + themeBorder, + themeColor, +} from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import DocLink from '../../../components/common/DocLink'; -import Link from '../../../components/common/Link'; -import { Alert, AlertProps } from '../../../components/ui/Alert'; +import DocumentationLink from '../../../components/common/DocumentationLink'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { queryToSearch } from '../../../helpers/urls'; import { IndexationNotificationType } from '../../../types/indexation'; @@ -36,26 +44,41 @@ export interface IndexationNotificationRendererProps { type?: IndexationNotificationType; } -const NOTIFICATION_VARIANTS: { [key in IndexationNotificationType]: AlertProps['variant'] } = { - [IndexationNotificationType.InProgress]: 'warning', - [IndexationNotificationType.InProgressWithFailure]: 'error', - [IndexationNotificationType.Completed]: 'success', - [IndexationNotificationType.CompletedWithFailure]: 'error', +const NOTIFICATION_COLORS: { + [key in IndexationNotificationType]: { background: ThemeColors; border: ThemeColors }; +} = { + [IndexationNotificationType.InProgress]: { + background: 'warningBackground', + border: 'warningBorder', + }, + [IndexationNotificationType.InProgressWithFailure]: { + background: 'errorBackground', + border: 'errorBorder', + }, + [IndexationNotificationType.Completed]: { + background: 'successBackground', + border: 'successBorder', + }, + [IndexationNotificationType.CompletedWithFailure]: { + background: 'errorBackground', + border: 'errorBorder', + }, }; export default function IndexationNotificationRenderer(props: IndexationNotificationRendererProps) { const { completedCount, total, type } = props; return ( - <div className={classNames({ 'indexation-notification-wrapper': type })}> - <Alert - className="indexation-notification-banner" - display="banner" - variant={type ? NOTIFICATION_VARIANTS[type] : 'success'} + <div className={type === undefined ? 'sw-hidden' : ''}> + <StyledBanner + className="sw-body-sm sw-py-3 sw-px-4 sw-gap-4" + type={type ?? IndexationNotificationType.Completed} aria-live="assertive" + role="alert" > {type !== undefined && ( - <div className="display-flex-center"> + <> + {renderIcon(type)} {type === IndexationNotificationType.Completed && renderCompletedBanner()} {type === IndexationNotificationType.CompletedWithFailure && @@ -66,20 +89,34 @@ export default function IndexationNotificationRenderer(props: IndexationNotifica {type === IndexationNotificationType.InProgressWithFailure && renderInProgressWithFailureBanner(completedCount as number, total as number)} - </div> + </> )} - </Alert> + </StyledBanner> </div> ); } +function renderIcon(type: IndexationNotificationType) { + switch (type) { + case IndexationNotificationType.Completed: + return <FlagSuccessIcon />; + case IndexationNotificationType.CompletedWithFailure: + case IndexationNotificationType.InProgressWithFailure: + return <FlagErrorIcon />; + case IndexationNotificationType.InProgress: + return <FlagWarningIcon />; + default: + return null; + } +} + function renderCompletedBanner() { - return <span className="spacer-right">{translate('indexation.completed')}</span>; + return <span>{translate('indexation.completed')}</span>; } function renderCompletedWithFailureBanner() { return ( - <span className="spacer-right"> + <span> <FormattedMessage defaultMessage={translate('indexation.completed_with_error')} id="indexation.completed_with_error" @@ -97,7 +134,7 @@ function renderCompletedWithFailureBanner() { function renderInProgressBanner(completedCount: number, total: number) { return ( <> - <span className="spacer-right"> + <span> <FormattedMessage id="indexation.in_progress" />{' '} <FormattedMessage id="indexation.features_partly_available" @@ -106,9 +143,9 @@ function renderInProgressBanner(completedCount: number, total: number) { }} /> </span> - <i className="spinner spacer-right" /> - <span className="spacer-right"> + <span className="sw-flex sw-items-center"> + <Spinner className="sw-mr-1 -sw-mb-1/2" /> {translateWithParameters( 'indexation.progression', completedCount.toString(), @@ -116,7 +153,7 @@ function renderInProgressBanner(completedCount: number, total: number) { )} </span> - <span className="spacer-right"> + <span> <FormattedMessage id="indexation.admin_link" defaultMessage={translate('indexation.admin_link')} @@ -132,7 +169,7 @@ function renderInProgressBanner(completedCount: number, total: number) { function renderInProgressWithFailureBanner(completedCount: number, total: number) { return ( <> - <span className="spacer-right"> + <span> <FormattedMessage id="indexation.in_progress" />{' '} <FormattedMessage id="indexation.features_partly_available" @@ -141,17 +178,15 @@ function renderInProgressWithFailureBanner(completedCount: number, total: number }} /> </span> - <i className="spinner spacer-right" /> - <span className="spacer-right"> + <span className="sw-flex sw-items-center"> + <Spinner className="sw-mr-1 -sw-mb-1/2" /> <FormattedMessage + tagName="span" id="indexation.progression_with_error" - defaultMessage={translateWithParameters( - 'indexation.progression_with_error', - completedCount.toString(), - total.toString(), - )} values={{ + count: completedCount, + total, link: renderBackgroundTasksPageLink( true, translate('indexation.progression_with_error.link'), @@ -181,8 +216,18 @@ function renderBackgroundTasksPageLink(hasError: boolean, text: string) { function renderIndexationDocPageLink() { return ( - <DocLink to="/instance-administration/reindexing/"> + <DocumentationLink className="sw-whitespace-nowrap" to="/instance-administration/reindexing/"> <FormattedMessage id="indexation.features_partly_available.link" /> - </DocLink> + </DocumentationLink> ); } + +const StyledBanner = styled.div<{ type: IndexationNotificationType }>` + display: flex; + align-items: center; + box-sizing: border-box; + width: 100%; + + background-color: ${({ type }) => themeColor(NOTIFICATION_COLORS[type].background)}; + border-bottom: ${({ type }) => themeBorder('default', NOTIFICATION_COLORS[type].border)}; +`; diff --git a/server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx b/server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx index 03444d3b0cc..c31b3f3d2f4 100644 --- a/server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx +++ b/server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { FlagMessage, Link } from 'design-system'; +import { CenteredLayout, FlagMessage, Link } from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import withIndexationContext, { @@ -37,8 +37,8 @@ export class PageUnavailableDueToIndexation extends React.PureComponent<WithInde render() { return ( - <div className="page-wrapper-simple"> - <FlagMessage className="sw-m-10" variant="info"> + <CenteredLayout className="sw-flex sw-justify-around"> + <FlagMessage className="sw-mt-32" variant="info"> <span className="sw-w-[290px]"> {translate('indexation.page_unavailable.description')} <span className="sw-ml-1"> @@ -58,7 +58,7 @@ export class PageUnavailableDueToIndexation extends React.PureComponent<WithInde </span> </span> </FlagMessage> - </div> + </CenteredLayout> ); } } diff --git a/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotification-test.tsx b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotification-test.tsx index 71426cd607f..d387a7e3554 100644 --- a/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotification-test.tsx +++ b/server/sonar-web/src/main/js/app/components/indexation/__tests__/IndexationNotification-test.tsx @@ -103,7 +103,7 @@ describe('Completed banner', () => { />, ); - expect(byText('indexation.progression_with_error').get()).toBeInTheDocument(); + expect(byText(/^indexation\.progression_with_error\.link/).get()).toBeInTheDocument(); rerender( <IndexationNotification diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavProjectBindingErrorNotif.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavProjectBindingErrorNotif.tsx index 9514729d9e4..803a51eaddf 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavProjectBindingErrorNotif.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavProjectBindingErrorNotif.tsx @@ -17,11 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import styled from '@emotion/styled'; +import { FlagWarningIcon, Link, themeBorder, themeColor } from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { PULL_REQUEST_DECORATION_BINDING_CATEGORY } from '../../../../apps/settings/constants'; -import Link from '../../../../components/common/Link'; -import { Alert } from '../../../../components/ui/Alert'; import { translate } from '../../../../helpers/l10n'; import { getProjectSettingsUrl } from '../../../../helpers/urls'; import { Component } from '../../../../types/types'; @@ -47,12 +47,20 @@ export default function ComponentNavProjectBindingErrorNotif( } return ( - <Alert display="banner" variant="warning"> - <FormattedMessage - defaultMessage={translate('component_navigation.pr_deco.error_detected_X')} - id="component_navigation.pr_deco.error_detected_X" - values={{ action }} - /> - </Alert> + <StyledBanner className="sw-body-sm sw-py-3 sw-px-4 sw-gap-4"> + <FlagWarningIcon /> + <FormattedMessage id="component_navigation.pr_deco.error_detected_X" values={{ action }} /> + </StyledBanner> ); } + +const StyledBanner = styled.div` + display: flex; + align-items: center; + box-sizing: border-box; + width: 100%; + + background-color: ${themeColor('warningBackground')}; + border-top: ${themeBorder('default', 'warningBorder')}; + border-bottom: ${themeBorder('default', 'warningBorder')}; +`; diff --git a/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx b/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx index 35ae56d1015..cd423d7a912 100644 --- a/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx +++ b/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx @@ -45,19 +45,17 @@ export function PromotionNotification(props: CurrentUserContextInterface) { }; return ( - <div className="toaster display-flex-center big-padded-left big-padded-right"> - <div className="toaster-icon spacer-right"> + <div className="toaster sw-flex sw-items-center sw-px-4"> + <div className="sw-mr-2"> <img alt="SonarQube + SonarLint" height={80} src={`${getBaseUrl()}/images/sq-sl.svg`} /> </div> - <div className="toaster-content flex-1 padded-left padded-right big-padded-top big-padded-bottom"> - <span className="toaster-title text-bold medium"> - {translate('promotion.sonarlint.title')} - </span> - <p className="spacer-top">{translate('promotion.sonarlint.content')}</p> + <div className="toaster-content sw-flex-1 sw-px-2 sw-py-4"> + <span className="sw-body-sm-highlight">{translate('promotion.sonarlint.title')}</span> + <p className="sw-mt-2">{translate('promotion.sonarlint.content')}</p> </div> <div className="toaster-actions spacer-left padded-left display-flex-column display-flex-center"> <a - className="button button-primary big-spacer-bottom" + className="button button-primary sw-mb-4" href="https://www.sonarsource.com/products/sonarlint/?referrer=sonarqube-welcome" rel="noreferrer" onClick={onClick} diff --git a/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.css b/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.css deleted file mode 100644 index baff8ae2ecf..00000000000 --- a/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.css +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.promote-update-notification.dismissable-alert-wrapper { - height: 42px; -} - -.promote-update-notification .dismissable-alert-banner { - margin-bottom: 0 !important; - width: 100%; - z-index: var(--globalBannerZIndex); -} diff --git a/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx b/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx index 04cc3b0c81a..4ed1ad44c54 100644 --- a/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx +++ b/server/sonar-web/src/main/js/app/components/update-notification/UpdateNotification.tsx @@ -17,11 +17,10 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Variant } from 'design-system'; +import { Banner, Variant } from 'design-system'; import { groupBy, isEmpty, mapValues } from 'lodash'; import * as React from 'react'; import { getSystemUpgrades } from '../../../api/system'; -import { Alert } from '../../../components/ui/Alert'; import DismissableAlert from '../../../components/ui/DismissableAlert'; import SystemUpgradeButton from '../../../components/upgrade/SystemUpgradeButton'; import { UpdateUseCase, sortUpgrades } from '../../../components/upgrade/utils'; @@ -34,7 +33,6 @@ import { Dict } from '../../../types/types'; import { CurrentUser, isLoggedIn } from '../../../types/users'; import withAppStateContext from '../app-state/withAppStateContext'; import withCurrentUserContext from '../current-user/withCurrentUserContext'; -import './UpdateNotification.css'; const MONTH_BEFOR_PREVIOUS_LTS_NOTIFICATION = 6; @@ -223,7 +221,7 @@ export class UpdateNotification extends React.PureComponent<Props, State> { <DismissableAlert alertKey={dismissKey} variant={MAP_VARIANT[useCase]} - className={`promote-update-notification it__upgrade-prompt-${useCase}`} + className={`it__promote-update-notification it__upgrade-prompt-${useCase}`} > {translate('admin_notification.update', useCase)} <SystemUpgradeButton @@ -233,14 +231,14 @@ export class UpdateNotification extends React.PureComponent<Props, State> { /> </DismissableAlert> ) : ( - <Alert variant={MAP_VARIANT[useCase]} className={`it__upgrade-prompt-${useCase}`}> + <Banner variant={MAP_VARIANT[useCase]} className={`it__upgrade-prompt-${useCase}`}> {translate('admin_notification.update', useCase)} <SystemUpgradeButton systemUpgrades={systemUpgrades} updateUseCase={useCase} latestLTS={latestLTS} /> - </Alert> + </Banner> ); } } diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/DismissableAlertComponent-test.tsx b/server/sonar-web/src/main/js/app/styles/GlobalStyles.tsx index 6fd6ead8b13..8db4ac406d6 100644 --- a/server/sonar-web/src/main/js/components/ui/__tests__/DismissableAlertComponent-test.tsx +++ b/server/sonar-web/src/main/js/app/styles/GlobalStyles.tsx @@ -17,31 +17,40 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import userEvent from '@testing-library/user-event'; + +import { Global, css, useTheme } from '@emotion/react'; +import { themeColor } from 'design-system/lib'; import React from 'react'; -import { renderComponent } from '../../../helpers/testReactTestingUtils'; -import { byLabelText, byText } from '../../../helpers/testSelector'; -import DismissableAlertComponent, { - DismissableAlertComponentProps, -} from '../DismissableAlertComponent'; +import twDefaultTheme from 'tailwindcss/defaultTheme'; + +export function GlobalStyles() { + const theme = useTheme(); + + return ( + <Global + styles={css` + body { + font-family: Inter, ${twDefaultTheme.fontFamily.sans.join(', ')}; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 400; -it('should render with children', () => { - render(); - expect(byText('testing').get()).toBeVisible(); -}); + color: ${themeColor('pageContent')({ theme })}; + background-color: ${themeColor('backgroundPrimary')({ theme })}; + } -it('calls onDismiss', async () => { - const onDismiss = jest.fn(); - render({ onDismiss }); - const user = userEvent.setup(); - await user.click(byLabelText('alert.dismiss').get()); - expect(onDismiss).toHaveBeenCalled(); -}); + a { + outline: none; + text-decoration: none; + color: ${themeColor('pageContent')({ theme })}; + } -function render(props: Partial<DismissableAlertComponentProps> = {}) { - return renderComponent( - <DismissableAlertComponent onDismiss={jest.fn()} variant="info" {...props}> - testing - </DismissableAlertComponent>, + ol, + ul { + padding-left: 0; + list-style: none; + } + `} + /> ); } diff --git a/server/sonar-web/src/main/js/app/styles/components/badges.css b/server/sonar-web/src/main/js/app/styles/components/badges.css deleted file mode 100644 index d1e3398155e..00000000000 --- a/server/sonar-web/src/main/js/app/styles/components/badges.css +++ /dev/null @@ -1,83 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.badge { - display: inline-block; - padding: 4px; - background-color: var(--barBorderColor); - border-radius: 3px; - font-size: var(--smallFontSize); - font-weight: 600; - color: var(--baseFontColor); - text-transform: uppercase; - white-space: nowrap; - line-height: 8px; -} -.badge:empty { - display: none; -} - -a.badge:hover, -a.badge:focus, -a.badge:active { - text-decoration: underline; -} -a.badge { - border-bottom: none; -} - -.list-group-item-heading > .badge { - float: right; - margin: 3px; -} -.list-group-item-heading > .badge + .badge { - margin-right: 5px; -} - -.badge-info { - background-color: var(--alertBackgroundInfo); - color: var(--alertTextInfo); -} - -.badge-success { - background-color: var(--alertBackgroundSuccess); - color: var(--alertTextSuccess); -} - -.badge-warning { - background-color: var(--alertBackgroundWarning); - color: var(--alertTextWarning); -} - -.badge-error { - background-color: var(--alertBackgroundError); - color: var(--alertTextError); -} - -.counter-badge { - color: var(--badgeBlueColor); - background-color: var(--badgeBlueBackground); - font-size: var(--smallFontSize); - padding: 0.3em 0.8em; - border-radius: 1em; -} - -.counter-badge:empty { - display: none; -} diff --git a/server/sonar-web/src/main/js/app/styles/components/boxed-group.css b/server/sonar-web/src/main/js/app/styles/components/boxed-group.css index 687f776c397..02b5cb0f788 100644 --- a/server/sonar-web/src/main/js/app/styles/components/boxed-group.css +++ b/server/sonar-web/src/main/js/app/styles/components/boxed-group.css @@ -51,26 +51,6 @@ margin: calc(2 * var(--gridSize)) -20px; } -.boxed-group-header { - position: relative; - z-index: 10; - padding: calc(2 * var(--gridSize)) 20px 0; -} - -.boxed-group-header > h2 { - display: inline-block; - vertical-align: middle; - line-height: var(--controlHeight); -} - -.boxed-group-actions { - position: relative; - z-index: 12; - float: right; - margin-top: calc(2 * var(--gridSize)); - margin-right: 20px; -} - .boxed-group-inner { padding: calc(2 * var(--gridSize)) 20px; } @@ -101,15 +81,6 @@ border-color: var(--info400); } -.boxed-group-accordion .boxed-group-header { - cursor: pointer; - padding-bottom: calc(2 * var(--gridSize)); -} - -.boxed-group-accordion.not-clickable .boxed-group-header { - cursor: default; -} - .boxed-group-accordion.not-clickable .boxed-group-accordion-title > svg { display: none; } diff --git a/server/sonar-web/src/main/js/app/styles/components/columns.css b/server/sonar-web/src/main/js/app/styles/components/columns.css deleted file mode 100644 index ed27c68728e..00000000000 --- a/server/sonar-web/src/main/js/app/styles/components/columns.css +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.columns { - margin-left: -10px; - margin-right: -10px; - overflow: hidden; -} - -.columns:before, -.columns:after { - display: table; - content: ''; - line-height: 0; -} - -.columns:after { - clear: both; -} - -.column-half { - float: left; - width: 50%; - padding: 0 10px; - box-sizing: border-box; -} - -.column-half.column-one { - margin: 0 25%; -} - -.flex-columns { - display: flex; -} - -.flex-column + .flex-column { - margin-left: 20px; -} - -.flex-column-full { - width: 100%; -} - -.flex-column-half { - width: 50%; -} - -.flex-column-third { - width: calc(100% / 3); -} diff --git a/server/sonar-web/src/main/js/app/styles/components/component-name.css b/server/sonar-web/src/main/js/app/styles/components/component-name.css deleted file mode 100644 index 78658b1d06b..00000000000 --- a/server/sonar-web/src/main/js/app/styles/components/component-name.css +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.component-name { - line-height: 16px; - font-size: var(--smallFontSize); -} - -.component-name:before, -.component-name:after { - display: table; - content: ''; - line-height: 0; -} - -.component-name:after { - clear: both; -} - -.component-name-parent { - float: left; - margin-right: 20px; -} - -.component-name-parent:last-child { - margin-right: 0; -} - -.component-name-path { - float: left; - clear: left; -} - -.component-name-parent + .component-name-path { - margin-top: 4px; -} - -.component-name-favorite { - margin-left: 4px; - padding: 0; -} diff --git a/server/sonar-web/src/main/js/app/styles/components/dropdowns.css b/server/sonar-web/src/main/js/app/styles/components/dropdowns.css deleted file mode 100644 index 0bf8a21f41d..00000000000 --- a/server/sonar-web/src/main/js/app/styles/components/dropdowns.css +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.dropdown { - position: relative; - display: inline-block; - vertical-align: middle; -} - -.dropdown-bottom-hint { - line-height: 16px; - margin-bottom: -5px; - padding: 5px 10px; - border-top: 1px solid var(--barBorderColor); - background-color: var(--barBackgroundColor); - color: var(--secondFontColor); - font-size: 11px; -} diff --git a/server/sonar-web/src/main/js/app/styles/components/global-loading.css b/server/sonar-web/src/main/js/app/styles/components/global-loading.css index 838578b6bdc..5aa658b9656 100644 --- a/server/sonar-web/src/main/js/app/styles/components/global-loading.css +++ b/server/sonar-web/src/main/js/app/styles/components/global-loading.css @@ -21,12 +21,42 @@ width: 300px; margin: 200px auto 0; white-space: nowrap; + font-family: + Inter, + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + 'Helvetica Neue', + Arial, + 'Noto Sans', + sans-serif, + 'Apple Color Emoji', + 'Segoe UI Emoji', + 'Segoe UI Symbol', + 'Noto Color Emoji'; + color: rgb(62, 67, 87); } -.global-loading .global-loading-spinner { +.global-loading-spinner { + display: inline-block; + box-sizing: border-box; vertical-align: middle; width: 80px; height: 80px; + border: 2px solid transparent; + border-radius: 625rem; + background: + linear-gradient(0deg, rgb(93, 108, 208) 50%, transparent 50% 100%) border-box, + linear-gradient(90deg, rgb(93, 108, 208) 25%, transparent 75% 100%) border-box; + mask: + linear-gradient(white 0 0) padding-box, + linear-gradient(white 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + animation: global-loading-spin 1s infinite linear; } .global-loading-text { @@ -36,3 +66,12 @@ font-size: 36px; font-weight: 300; } + +@keyframes global-loading-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(-360deg); + } +} diff --git a/server/sonar-web/src/main/js/app/styles/components/issues.css b/server/sonar-web/src/main/js/app/styles/components/issues.css deleted file mode 100644 index 0d066e7e6c6..00000000000 --- a/server/sonar-web/src/main/js/app/styles/components/issues.css +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.issue-list { - margin: 10px 0; -} - -.issue-list, -.issue { - max-width: 980px; -} - -.issue-filters-list { - /* - * On Firefox on Windows, the scrollbar hides the sidebar's content. - * Using 'scrollbar-gutter:stable' is a workaround to ensure consistency with other browsers. - * @see https://bugzilla.mozilla.org/show_bug.cgi?id=764076 - * @see https://discuss.sonarsource.com/t/unnecessary-horizontal-scrollbar-on-issues-page/14889/4 - */ - scrollbar-gutter: stable; -} diff --git a/server/sonar-web/src/main/js/app/styles/components/list-groups.css b/server/sonar-web/src/main/js/app/styles/components/list-groups.css deleted file mode 100644 index c6774b23de2..00000000000 --- a/server/sonar-web/src/main/js/app/styles/components/list-groups.css +++ /dev/null @@ -1,87 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.list-group { - margin-bottom: 20px; - padding-left: 0; -} - -.list-group-item, -button.list-group-item { - position: relative; - z-index: var(--normalZIndex); - display: block; - margin-bottom: -1px; - padding: 5px 10px; - border: 1px solid transparent; - width: 100%; - box-sizing: border-box; - text-align: left; -} - -.list-group-item.depth-1 { - padding-left: 31px; -} - -.list-group-item.depth-2 { - padding-left: 51px; -} - -.list-group-item.depth-3 { - padding-left: 71px; -} - -.list-group-item:last-child { - margin-bottom: 0; -} - -.list-group-item.active, -.list-group-item.active:hover, -.list-group-item.active:focus { - z-index: var(--aboveNormalZIndex); - border-color: var(--blue) !important; - background-color: var(--lightBlue); -} - -.list-group-item:hover { - z-index: var(--aboveNormalZIndex); - border-color: var(--blue) !important; -} - -.list-group-item + .list-group-item { - border-top-color: var(--barBorderColor); -} - -a.list-group-item { - color: var(--baseFontColor); - transition: none; -} - -.list-group-item-heading { - margin-top: 5px; - margin-bottom: 5px; - text-overflow: ellipsis; - overflow: hidden; -} - -.list-group-item-heading:after { - content: ''; - display: table; - clear: both; -} diff --git a/server/sonar-web/src/main/js/app/styles/components/menu.css b/server/sonar-web/src/main/js/app/styles/components/menu.css deleted file mode 100644 index 492d6283643..00000000000 --- a/server/sonar-web/src/main/js/app/styles/components/menu.css +++ /dev/null @@ -1,188 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.menu { - min-width: 160px; - padding: 5px 0; - list-style: none; - font-size: var(--smallFontSize); - text-align: left; - background-color: #fff; - background-clip: padding-box; -} - -.menu:not(:last-of-type) { - padding-bottom: 12px; -} - -.menu + .menu, -.menu + .menu-header { - border-top: 1px solid var(--barBorderColor); -} - -.menu.is-container { - padding: 5px; -} - -.menu-item, -.menu > li > a, -.menu > li > button, -.menu > li > span { - display: block; - padding: 4px 16px; - line-height: 14px; - clear: both; - font-weight: normal; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.menu > li > a, -.menu > li > button { - color: var(--neutral800); - border-width: 0 0 0 2px; - border-style: solid; - border-color: transparent; - transition: none; -} - -.menu > li > button { - text-align: left; - width: 100%; -} - -.menu > li > a.disabled { - color: var(--disableGrayText) !important; - cursor: not-allowed !important; - pointer-events: none !important; -} - -.menu > li > a.text-muted { - color: var(--secondFontColor); -} - -.menu > li > a:hover, -.menu > li > a.hover, -.menu > li > button:hover { - background-color: var(--neutral50); - border-left-color: var(--blacka60); -} - -.menu > li > a.active, -.menu > li > button.active { - background-color: var(--info50); - border-left-color: var(--info500); -} - -.menu > li > a.active:hover, -.menu > li > a.active.hover, -.menu > li > button.active:hover { - background-color: var(--info100); -} - -.menu.menu-vertically-limited { - max-height: 300px; - overflow-y: auto; -} - -.menu .divider { - height: 1px; - margin: 6px 0; - overflow: hidden; - background-color: var(--barBorderColor); -} - -.menu-vertically-limited.with-top-separator { - border-top: 1px solid #e6e6e6; -} - -.menu-vertically-limited.with-bottom-separator { - border-bottom: 1px solid #e6e6e6; -} - -.menu .menu-footer > a > span { - border-bottom: 1px solid var(--gray80); - color: var(--secondFontColor); -} - -.menu .menu-footer-note { - opacity: 0; - transition: opacity 0.3s ease; -} - -.menu .menu-footer.active .menu-footer-note { - opacity: 1; -} - -.menu-search { - position: relative; - padding: var(--gridSize) calc(2 * var(--gridSize)) 0; -} - -.menu-search .search-box, -.menu-search .search-box-input { - max-width: none; - min-width: 240px; -} - -.menu-search ~ .menu > li > a:hover, -.menu-search ~ .menu > li > a:focus { - background-color: transparent; -} - -.menu-search ~ .menu > .active > a, -.menu-search ~ .menu > li > .active, -.menu-search ~ .menu > .active > a:hover, -.menu-search ~ .menu > li > .active:hover, -.menu-search ~ .menu > .active > a:focus, -.menu-search ~ .menu > li > .active:focus { - background-color: var(--barBackgroundColor); -} - -.menu-message { - display: block; - padding: 4px 16px; - line-height: 16px; -} - -.menu-header { - padding: var(--gridSize); - margin: -8px; - font-size: 12px; - color: var(--neutral600); - white-space: nowrap; - line-height: unset; -} - -.menu-header + ul { - padding-top: 8px; -} - -.menu-header.no-margin + ul { - padding-top: 0; -} - -.menu-header.no-margin { - margin: 0; -} - -.divider + .menu-header { - padding-top: calc(var(--gridSize) - 5px); -} diff --git a/server/sonar-web/src/main/js/app/styles/components/page.css b/server/sonar-web/src/main/js/app/styles/components/page.css index 812f7cf6cc9..37962ef9a7c 100644 --- a/server/sonar-web/src/main/js/app/styles/components/page.css +++ b/server/sonar-web/src/main/js/app/styles/components/page.css @@ -61,20 +61,6 @@ flex: 1 0 auto; } -.page-wrapper-simple { - display: flex; - justify-content: center; - align-items: center; - margin: 100px 0; -} - -.page-simple { - width: 400px; - padding: 40px; - border: 1px solid var(--barBorderColor); - background-color: #fff; -} - .page-header { position: relative; margin-bottom: 20px; @@ -91,12 +77,6 @@ clear: both; } -.page-header .spinner { - position: relative; - top: 3px; - margin-left: 8px; -} - .page-title { float: left; margin-bottom: 0; @@ -115,10 +95,6 @@ margin: 3px 0; } -.page-actions .spinner { - top: 0 !important; -} - .page-description { float: left; clear: left; diff --git a/server/sonar-web/src/main/js/app/styles/components/panels.css b/server/sonar-web/src/main/js/app/styles/components/panels.css deleted file mode 100644 index 584d3390d36..00000000000 --- a/server/sonar-web/src/main/js/app/styles/components/panels.css +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.panel { - padding: 10px; -} - -.panel:not(:last-child) { - border-bottom: 1px solid var(--barBorderColor); -} - -.panel-vertical { - padding-left: 0; - padding-right: 0; -} - -.panel-white { - border: 1px solid var(--barBorderColor); - background-color: #fff; -} - -.panel-warning { - border: 1px solid var(--alertBorderWarning); - background-color: var(--alertBackgroundWarning); - color: #8a6d3b; -} diff --git a/server/sonar-web/src/main/js/app/styles/components/spinner.css b/server/sonar-web/src/main/js/app/styles/components/spinner.css deleted file mode 100644 index c33b3aa34fa..00000000000 --- a/server/sonar-web/src/main/js/app/styles/components/spinner.css +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.spinner { - position: relative; - vertical-align: middle; - width: 16px; - height: 16px; - border: 2px solid var(--info500); - border-radius: 50%; - animation: spin 0.75s infinite linear; -} - -.spinner:before, -.spinner:after { - left: -2px; - top: -2px; - display: none; - position: absolute; - content: ''; - width: inherit; - height: inherit; - border: inherit; - border-radius: inherit; -} - -.spinner, -.spinner:before, -.spinner:after { - display: inline-block; - box-sizing: border-box; - border-color: transparent; - border-top-color: var(--info500); - animation-duration: 1.2s; -} - -.spinner:before { - transform: rotate(120deg); -} - -.spinner:after { - transform: rotate(240deg); -} - -@keyframes spin { - from { - transform: rotate(0deg); - } - - to { - transform: rotate(360deg); - } -} diff --git a/server/sonar-web/src/main/js/app/styles/components/ui.css b/server/sonar-web/src/main/js/app/styles/components/ui.css deleted file mode 100644 index 8ec19539497..00000000000 --- a/server/sonar-web/src/main/js/app/styles/components/ui.css +++ /dev/null @@ -1,85 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.shortcut-button { - display: inline-block; - min-width: 24px; - height: var(--controlHeight); - line-height: 21px; - padding: 0 4px; - box-sizing: border-box; - border: 1px solid #ccc; - border-radius: 3px; - background-image: linear-gradient(to bottom, #f5f5f5, #eee); - box-shadow: - inset 0 1px 0 #fff, - 0 1px 0 #ccc; - color: var(--secondFontColor); - font-size: 11px; - text-align: center; -} - -.shortcut-button-small { - min-width: 16px; - height: 16px; - line-height: 14px; - margin-left: 4px; - margin-right: 4px; -} - -.shortcut-button-tiny { - width: 14px; - min-width: auto; - padding: 0; - height: 14px; - line-height: inherit; - font-size: 6px; -} - -.page-shortcuts-tooltip { - line-height: 12px; -} - -.identity-provider { - display: inline-block; - line-height: 14px; - padding: 2px 5px; - border: 1px solid rgba(0, 0, 0, 0.15); - border-radius: 3px; - box-sizing: border-box; - background-color: var(--darkBlue); - font-size: var(--smallFontSize); - color: #fff; -} - -.analysis-version { - display: inline-block; - vertical-align: middle; - height: 20px; - padding: 0 8px; - background-color: var(--primary400); - border-radius: 2px; - line-height: 20px; - font-size: var(--smallFontSize); - color: var(--white); - white-space: nowrap; - text-align: center; - font-weight: bold; - letter-spacing: 0; -} diff --git a/server/sonar-web/src/main/js/app/styles/init/base.css b/server/sonar-web/src/main/js/app/styles/init/base.css index 41f506c63e7..a7f14eca985 100644 --- a/server/sonar-web/src/main/js/app/styles/init/base.css +++ b/server/sonar-web/src/main/js/app/styles/init/base.css @@ -29,12 +29,12 @@ } *:focus-visible { - outline: 2px dotted var(--primary400); + outline: 2px dotted #297bae; } html, body { - background-color: var(--barBackgroundColor); + background-color: white; } body { diff --git a/server/sonar-web/src/main/js/app/styles/init/forms.css b/server/sonar-web/src/main/js/app/styles/init/forms.css deleted file mode 100644 index 64c35c5b8f8..00000000000 --- a/server/sonar-web/src/main/js/app/styles/init/forms.css +++ /dev/null @@ -1,220 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -/* - * Inputs - */ -input[type='text'], -input[type='password'], -input[type='email'], -input[type='search'], -input[type='date'], -input[type='number'], -textarea, -select { - border: 1px solid var(--gray80); - box-sizing: border-box; - border-radius: 2px; - background: #fff; - color: var(--baseFontColor); - transition: border-color 0.2s ease; -} - -input[type='text']:active, -input[type='password']:active, -input[type='email']:active, -input[type='search']:active, -input[type='date']:active, -input[type='number']:active, -textarea:active, -select:active, -input[type='text']:focus, -input[type='password']:focus, -input[type='email']:focus, -input[type='search']:focus, -input[type='date']:focus, -input[type='number']:focus, -textarea:focus, -select:focus { - border-color: var(--blue); - box-shadow: none; - outline: none; -} - -input[type='text']:invalid, -input[type='password']:invalid, -input[type='email']:invalid, -input[type='search']:invalid, -input[type='date']:invalid, -input[type='number']:invalid, -textarea:invalid, -select:invalid { - box-shadow: none; - outline: none; -} - -input::placeholder, -textarea::placeholder { - color: var(--neutral600); - opacity: 0.9; -} - -input[type='text'].is-valid, -input[type='password'].is-valid, -input[type='email'].is-valid, -input[type='search'].is-valid, -input[type='date'].is-valid, -input[type='number'].is-valid, -textarea.is-valid, -select.is-valid { - border-color: var(--green); -} - -input[type='text'].is-invalid, -input[type='password'].is-invalid, -input[type='email'].is-invalid, -input[type='search'].is-invalid, -input[type='date'].is-invalid, -input[type='number'].is-invalid, -textarea.is-invalid, -select.is-invalid { - border-color: var(--red); -} - -input.disabled, -input:disabled, -textarea.disabled, -textarea:disabled, -select.disabled, -select:disabled { - color: var(--disableGrayText) !important; - border-color: var(--disableGrayBorder) !important; - background: var(--disableGrayBg) !important; - cursor: not-allowed !important; - pointer-events: none !important; - box-shadow: none !important; -} - -input[type='text'], -input[type='password'], -input[type='email'], -input[type='search'], -input[type='date'], -input[type='number'] { - height: var(--controlHeight); - padding: 0 6px; -} - -input[type='search'] { - -webkit-appearance: textfield; -} - -input[type='search']::-webkit-search-decoration { - -webkit-appearance: none; -} - -textarea { - padding: 3px; -} - -textarea.width-100 { - max-width: 100%; -} - -textarea.fixed-width { - resize: vertical; -} - -select { - height: var(--controlHeight); - line-height: var(--controlHeight); -} - -.input-tiny { - width: 60px !important; -} - -.input-small { - width: 100px !important; -} - -.input-medium { - width: 150px !important; -} - -.input-large { - width: 200px !important; -} - -.input-super-large { - width: 100% !important; - max-width: 300px; - min-width: 200px; -} - -.input-ghost { - padding: 0 !important; - border: none !important; - background-color: transparent !important; -} - -.input-clear { - background-color: transparent !important; -} - -.input-code { - font-family: var(--sourceCodeFontFamily); - font-size: var(--smallFontSize); -} - -em.mandatory { - color: var(--mandatoryFieldColor); - font-style: italic; -} - -.form-field { - clear: both; - display: block; - padding-bottom: calc(2 * var(--gridSize)); -} - -.form-field label { - display: block; - font-weight: bold; - padding-bottom: calc(var(--gridSize) / 2); -} - -.form-field-description { - line-height: 1.4; - color: var(--secondFontColor); - font-size: var(--smallFontSize); - overflow: hidden; - text-overflow: ellipsis; - margin-top: 2px; -} - -.form-field input[type='text'], -.form-field input[type='email'], -.form-field input[type='password'], -.form-field textarea, -.form-field select, -.form-field .react-select, -.form-field .Select { - width: 250px; -} diff --git a/server/sonar-web/src/main/js/app/styles/init/icons.css b/server/sonar-web/src/main/js/app/styles/init/icons.css deleted file mode 100644 index 2206cde6208..00000000000 --- a/server/sonar-web/src/main/js/app/styles/init/icons.css +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -[class^='icon-'], -[class*=' icon-'] { - line-height: 1; - vertical-align: middle; -} - -a[class^='icon-'], -a[class*=' icon-'] { - border-bottom: none; -} - -/* - * Colors - */ -.icon-gray { - color: #999; -} - -.icon-gray path { - fill: #999; -} - -.icon-color-link { - color: var(--darkBlue); -} - -/* - * Common - */ -.icon-outline { - transition: all 0.2s ease !important; -} - -.icon-outline path { - stroke: var(--secondFontColor); - stroke-width: 1.41421356; - stroke-opacity: 1; - fill-opacity: 0; - vector-effect: non-scaling-stroke; - transition: all 0.2s ease; -} - -.icon-outline.is-filled path { - fill: currentColor; - stroke: currentColor; - fill-opacity: 1; -} diff --git a/server/sonar-web/src/main/js/app/styles/init/links.css b/server/sonar-web/src/main/js/app/styles/init/links.css deleted file mode 100644 index b0e811ec87a..00000000000 --- a/server/sonar-web/src/main/js/app/styles/init/links.css +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -a { - border-bottom: 1px solid var(--primarya40); - color: var(--primary); - cursor: pointer; - outline: none; - text-decoration: none; - transition: - border-bottom-color 0.2s ease, - color 0.2s ease; -} - -a:hover, -a:active, -a:focus { - border-bottom-color: var(--primary); -} - -a svg, -a img { - vertical-align: middle; -} - -.link-no-underline { - border-bottom-color: transparent !important; -} - -.link-no-underline:hover { - border-bottom-color: var(--primary) !important; -} - -.link-rating, -.link-rating:hover { - border-bottom: 0 !important; -} diff --git a/server/sonar-web/src/main/js/app/styles/init/lists.css b/server/sonar-web/src/main/js/app/styles/init/lists.css deleted file mode 100644 index 4447df2b394..00000000000 --- a/server/sonar-web/src/main/js/app/styles/init/lists.css +++ /dev/null @@ -1,108 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -ol, -ul { - padding-left: 0; - list-style: none; -} - -.list-styled { - margin-bottom: 10px; - padding-left: 40px; -} - -.list-styled.no-padding { - padding-left: calc(var(--gridSize) * 2); -} - -ul.list-styled { - list-style: disc; -} - -ol.list-styled { - list-style: decimal; -} - -.list-inline { - padding-left: 0; - margin-left: -5px; - list-style: none; -} - -.list-breadcrumbs > li { - display: inline-block; -} - -.list-breadcrumbs > li:not(:first-of-type)::before { - content: '/' / ''; - margin-inline: 3px; -} - -ul.list-inline > li, -div.list-inline > div { - display: inline-block; - vertical-align: top; - padding-right: 5px; - padding-left: 5px; -} - -.list-spaced { - margin-bottom: 10px; - list-style: none; -} - -.list-spaced > li { - margin-top: 10px; -} - -.list-item-checkable-link { - cursor: pointer; -} - -.list-item-checkable-link:focus { - outline: none; -} - -.list-item-checkable-link.disabled { - opacity: 0.7; -} - -.list-item-checkable-link.disabled a::before { - background-color: var(--gray80); - border-color: var(--gray80); -} - -dl { - margin-top: 0; - margin-bottom: 20px; -} - -dt, -dd { - line-height: 1.42857143; -} - -dt { - font-weight: bold; -} - -dd { - margin-left: 0; -} diff --git a/server/sonar-web/src/main/js/app/styles/init/misc.css b/server/sonar-web/src/main/js/app/styles/init/misc.css index d1d01be68f3..df466f2fa8a 100644 --- a/server/sonar-web/src/main/js/app/styles/init/misc.css +++ b/server/sonar-web/src/main/js/app/styles/init/misc.css @@ -34,32 +34,11 @@ th.hide-overflow { overflow: hidden; } -.hidden { - display: none !important; - visibility: hidden !important; -} - -.invisible { - visibility: hidden; -} - .note { color: var(--secondFontColor); font-size: var(--smallFontSize); } -.nudged-up { - margin-top: -1px; -} - -.nudged-down { - margin-top: 1px; -} - -.null-spacer-top { - margin-top: 0 !important; -} - .null-spacer-bottom { margin-bottom: 0 !important; } @@ -84,46 +63,6 @@ th.hide-overflow { margin-top: 8px !important; } -.big-spacer { - margin: 16px !important; -} - -.big-spacer-left { - margin-left: 16px !important; -} - -.big-spacer-right { - margin-right: 16px !important; -} - -.big-spacer-bottom { - margin-bottom: 16px !important; -} - -.big-spacer-top { - margin-top: 16px !important; -} - -.huge-spacer { - margin: 40px !important; -} - -.huge-spacer-bottom { - margin-bottom: 40px !important; -} - -.huge-spacer-top { - margin-top: 40px !important; -} - -.huge-spacer-left { - margin-left: 40px !important; -} - -.huge-spacer-right { - margin-right: 40px !important; -} - .little-spacer { margin: 4px !important; } @@ -148,18 +87,10 @@ th.hide-overflow { padding: var(--gridSize) !important; } -.little-padded { - padding: calc(var(--gridSize) / 2) !important; -} - .big-padded { padding: calc(2 * var(--gridSize)) !important; } -.padded-top { - padding-top: var(--gridSize) !important; -} - .padded-right { padding-right: var(--gridSize) !important; } @@ -172,45 +103,10 @@ th.hide-overflow { padding-left: var(--gridSize) !important; } -.little-padded-top { - padding-top: calc(var(--gridSize) / 2) !important; -} - -.little-padded-right { - padding-right: calc(var(--gridSize) / 2) !important; -} - -.little-padded-bottom { - padding-bottom: calc(var(--gridSize) / 2) !important; -} - -.little-padded-left { - padding-left: calc(var(--gridSize) / 2) !important; -} - -.big-padded-top { - padding-top: calc(2 * var(--gridSize)); -} -.big-padded-bottom { - padding-bottom: calc(2 * var(--gridSize)); -} - .big-padded-right { padding-right: calc(2 * var(--gridSize)); } -.big-padded-left { - padding-left: calc(2 * var(--gridSize)); -} - -.huge-padded-top { - padding-top: 40px; -} - -.huge-padded-bottom { - padding-bottom: 40px; -} - td.little-spacer-left { padding-left: 4px !important; } @@ -235,60 +131,10 @@ td.spacer-top { padding-top: 8px !important; } -td.big-spacer-left, -th.big-spacer-left { - padding-left: 16px !important; -} - -td.big-spacer-right { - padding-right: 16px !important; -} - -td.big-spacer-bottom { - padding-bottom: 16px !important; -} - -td.big-spacer-top { - padding-top: 16px !important; -} - -td.huge-spacer-right, -th.huge-spacer-right { - padding-right: 40px !important; -} - .pull-left { float: left !important; } -.pull-right { - float: right !important; -} - -.borderless { - border: none !important; -} - -.bordered { - border: 1px solid var(--barBorderColor); -} - -.bordered-left { - border-left: 1px solid var(--barBorderColor); -} - -.bordered-right { - border-right: 1px solid var(--barBorderColor); -} - -.bordered-bottom { - border-bottom: 1px solid var(--barBorderColor); -} - -.bordered-top { - border-top: 1px solid var(--barBorderColor); -} - .overflow-hidden { overflow: hidden !important; } @@ -297,78 +143,14 @@ th.huge-spacer-right { overflow-y: auto !important; } -.max-width-100 { - max-width: 100% !important; -} - -.max-width-80 { - max-width: 80% !important; -} - -.max-width-60 { - max-width: 60% !important; -} - .width-100 { width: 100% !important; } -.width-80 { - width: 80% !important; -} - -.width-60 { - width: 60% !important; -} - -.width-55 { - width: 55% !important; -} - -.width-50 { - width: 50% !important; -} - -.width-40 { - width: 40% !important; -} - -.width-30 { - width: 30% !important; -} - -.width-25 { - width: 25% !important; -} - -.width-20 { - width: 20% !important; -} - -.width-15 { - width: 15% !important; -} - -.width-10 { - width: 10% !important; -} - .abs-width-100 { width: 100px !important; } -.abs-width-150 { - width: 150px !important; -} - -.abs-width-240 { - width: 240px !important; -} - -.abs-width-300 { - width: 300px !important; -} - .abs-width-400 { width: 400px !important; } @@ -377,58 +159,6 @@ th.huge-spacer-right { width: 600px !important; } -.abs-width-800 { - width: 800px !important; -} - -.abs-height-50 { - height: 50px !important; -} - -.abs-height-100 { - height: 100% !important; -} - -.max-height-100 { - max-height: 100% !important; -} - -.justify { - margin-bottom: -1em; - text-align: justify; -} - -.justify > .ib { - display: inline-block; -} - -.justify:after { - display: inline-block; - width: 100%; - content: ' '; -} - -.first-letter-uppercase::first-letter { - text-transform: uppercase; -} - -.disabled-pointer-events { - pointer-events: none !important; -} - -.display-block { - display: block !important; -} - -.display-inline-block { - display: inline-block !important; -} - -.display-flex-row { - display: flex !important; - flex-direction: row; -} - .display-flex-column { display: flex !important; flex-direction: column; @@ -439,108 +169,29 @@ th.huge-spacer-right { align-items: center; } -.display-flex-justify-start { - display: flex !important; - justify-content: flex-start !important; -} - .display-flex-justify-center { display: flex !important; justify-content: center; } -.display-flex-justify-end { - display: flex !important; - justify-content: flex-end; -} - -.display-flex-space-around { - display: flex !important; - justify-content: space-around; -} - -.display-flex-space-between { - display: flex !important; - justify-content: space-between; -} - -.display-flex-stretch { - display: flex !important; - align-items: stretch; -} - .display-flex-start { display: flex !important; align-items: flex-start !important; } -.display-flex-end { - display: flex !important; - align-items: flex-end; -} - -.display-flex-wrap { - display: flex !important; - flex-wrap: wrap; -} - -.display-inline-flex-baseline { - display: inline-flex !important; - align-items: baseline; -} - -.display-inline-flex-start { - display: inline-flex !important; - align-items: flex-start; -} - .display-inline-flex-center { display: inline-flex !important; align-items: center; } -.position-absolute { - position: absolute !important; -} - -.position-relative { - position: relative !important; -} - -.rounded { - border-radius: 2px; -} - .flex-1 { flex: 1; } -.flex-1-0-auto { - flex: 1 0 auto; -} - -.flex-0 { - flex: 0 0 auto; -} - .flex-grow { flex-grow: 1; } -.flex-shrink { - flex-shrink: 1; - min-width: 0; -} - -.space-between { - justify-content: space-between !important; -} - -.new-loading { - opacity: 0.5; - transition: opacity 0.5s ease; -} - .slash-separator { margin-left: 5px; margin-right: 5px; @@ -550,99 +201,3 @@ th.huge-spacer-right { content: '/'; color: rgba(68, 68, 68, 0.3); } - -.horizontal-pipe-separator { - display: flex; - align-items: center; - margin-top: calc(4 * var(--gridSize)); - margin-bottom: calc(4 * var(--gridSize)); -} - -.horizontal-pipe-separator > .horizontal-separator { - margin: 0 4px; -} - -.horizontal-separator { - min-width: 16px; - height: 1px; - flex-grow: 1; - background-color: var(--barBorderColor); -} - -.vertical-separator { - width: 1px; - min-height: 16px; - flex-grow: 1; - background-color: var(--barBorderColor); -} - -.vertical-pipe-separator { - display: flex; - flex-direction: column; - margin-left: 60px; - margin-right: 60px; -} - -.vertical-pipe-separator > .vertical-separator { - margin: 4px auto; -} - -.capitalize { - text-transform: capitalize !important; -} - -.cursor-pointer { - cursor: pointer; -} - -.cursor-not-allowed { - cursor: not-allowed !important; -} - -.no-outline, -.no-outline:focus { - outline: none !important; -} - -.bg-danger { - background-color: var(--red); - color: #fff; -} - -.bg-warning { - background-color: var(--alertBackgroundWarning); - color: var(--alertTextWarning); -} - -.bg-info { - background-color: var(--blue); - color: #fff; -} - -.bg-success { - background-color: var(--green); - color: #fff; -} - -.bg-muted { - background-color: var(--barBackgroundColor); - color: inherit; -} - -.muted { - color: var(--neutral600); -} - -.leak-box { - background-color: var(--leakPrimaryColor); - border: 1px solid var(--leakSecondaryColor); - padding: 4px 6px; -} - -.break-word { - word-break: break-word; -} - -.no-margin-bottom { - margin-bottom: 0 !important; -} diff --git a/server/sonar-web/src/main/js/app/styles/init/tables.css b/server/sonar-web/src/main/js/app/styles/init/tables.css deleted file mode 100644 index 8ac6c5d3d6f..00000000000 --- a/server/sonar-web/src/main/js/app/styles/init/tables.css +++ /dev/null @@ -1,301 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -table { - border-collapse: collapse; - border-spacing: 0; -} - -table.form td { - padding: 2px 5px; - vertical-align: top; -} - -table.form th { - padding: 2px 5px; - font-weight: 600; -} - -table.form td.keyCell { - width: 1%; - white-space: nowrap; - text-align: right; - font-weight: bold; - vertical-align: top; -} - -table.form td img { - vertical-align: bottom; -} - -table.spaced th { - font-weight: bold; - color: #333; - padding: 4px 5px; -} - -table.spaced td { - padding: 3px 5px; - line-height: 18px; -} - -table.spaced td img { - vertical-align: text-bottom; -} - -table.spacedicon th { - font-weight: bold; - color: #333; - padding: 4px 5px; -} - -table.spacedicon td { - padding: 0 5px; - height: 24px; -} - -.thin { - width: 1%; -} - -.formError { - display: inline-block; - background-color: var(--orange); - color: #000; - padding: 0 5px; -} - -.table > thead > tr > th { - border-top: 0 none; - font-weight: bold; - line-height: 16px; - padding: 4px 5px; - vertical-align: bottom; -} - -.table > tbody > tr > td { - line-height: 16px; - padding: 4px 5px; - vertical-align: top; -} - -.table > tfoot > tr > td { - font-size: 93%; - color: var(--secondFontColor); - padding: 4px 5px; -} - -.table > tfoot > tr > td a { - color: var(--secondFontColor); -} - -.hoverable:hover { - background-color: var(--lightBlue); -} - -.hoverable:hover a { - color: #111; -} - -.odd { - background-color: #fff; -} - -.even { - background-color: #f5f5f5; -} - -.odd.selected, -.even.selected, -.odd.selected a, -.even.selected a, -.even.selected span:not(.rating), -.odd.selected span:not(.rating) { - background-color: #d9edf7; - color: var(--baseFontColor); -} - -.table-cell-doc { - position: absolute; - z-index: var(--aboveNormalZIndex); - right: -8px; -} - -th > .table-cell-doc { - top: 50%; - margin-top: -6px; -} - -td.sep { - width: 10px; -} - -table.matrix tfoot td { - padding: 3px 5px; - line-height: 18px; -} - -table.data, -table.spaced { - width: 100%; -} - -table.data td.small, -table.data th.small { - padding: 0; - white-space: nowrap; -} - -table.data > caption { - padding: 8px 10px; - text-align: center; - font-weight: bold; -} - -table.data > thead > tr > th { - position: relative; - vertical-align: top; - line-height: 18px; - padding: 8px 10px; - border-bottom: 1px solid var(--barBorderColor); - font-weight: 600; -} - -table.data > thead > tr > th > .small { - display: block; - line-height: 1.4; - font-weight: 400; -} - -table.data > tfoot > tr > td { - font-size: 93%; - color: var(--secondFontColor); - padding: 5px; -} - -table.data > tbody > tr > td { - position: relative; - padding: 8px 10px; - line-height: 16px; -} - -table.data > tbody > tr > td.text-middle { - vertical-align: middle; -} - -.data thead tr.total { - background-color: var(--gray94); - font-weight: normal; - border: 1px solid #ddd; -} - -.data thead tr.total th { - font-weight: normal; -} - -.data tr.blank, -.data tr.blank > td, -.data td.blank { - background-color: #fff !important; - line-height: 15px; -} - -.data tr.highlight { - background-color: var(--lightBlue); -} - -.data input, -.data select, -.data button { - vertical-align: middle; -} - -table.data.condensed > tbody > tr > td { - padding-top: 5px; - padding-bottom: 5px; -} - -table.data tr.subheader th { - font-size: var(--smallFontSize); - border-bottom: none; -} - -table.data:not(.boxed-padding) > thead:after { - display: block; - line-height: 5px; - content: '\200C'; -} - -table.data.boxed-padding > thead > tr > th { - padding-top: 24px; -} - -table.data.boxed-padding > thead > tr > th:first-child, -table.data.boxed-padding > tbody > tr > td:first-child, -table.data.boxed-padding > thead > tr > th:last-child, -table.data.boxed-padding > tbody > tr > td:last-child { - width: 20px; - padding: 8px 0; -} - -table.data.no-outer-padding > thead > tr > th:first-child, -table.data.no-outer-padding > tbody > tr > td:first-child { - padding-left: 0; -} - -table.data.no-outer-padding > thead > tr > th:last-child, -table.data.no-outer-padding > tbody > tr > td:last-child { - padding-right: 0; -} - -table.data.boxed-padding > thead + tbody > tr:first-child > td { - padding-top: 16px; -} - -table.data.zebra-hover > tbody > tr:hover { - background-color: var(--rowHoverHighlight) !important; -} - -table.data.zebra > tbody > tr.selected { - background-color: #d9edf7 !important; -} - -table.data.zebra:not(.zebra-inversed) > tbody > tr:nth-child(even) { - background-color: #f5f5f5; -} - -table.data.zebra.zebra-inversed > tbody > tr:nth-child(odd) { - background-color: #f5f5f5; -} - -table#project-history tr > td { - vertical-align: top; -} - -table.fixed { - table-layout: fixed; -} - -table.fixed th.action-small { - width: 30px; -} - -table.fixed th.action { - width: 50px; -} diff --git a/server/sonar-web/src/main/js/app/styles/init/type.css b/server/sonar-web/src/main/js/app/styles/init/type.css deleted file mode 100644 index 1ed45b8b67b..00000000000 --- a/server/sonar-web/src/main/js/app/styles/init/type.css +++ /dev/null @@ -1,304 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -html, -body { - color: var(--baseFontColor); -} - -body { - font-family: var(--baseFontFamily); - font-size: var(--baseFontSize); - line-height: 1.23076923; -} - -h1, -.h1 { - line-height: var(--controlHeight); - color: var(--baseFontColor); - font-size: var(--bigFontSize); - font-weight: 400; -} - -h1 img, -.h1 img, -h1 svg, -.h1 svg { - vertical-align: middle; - transform: translateY(-1px); -} - -h2, -.h2 { - line-height: var(--controlHeight); - color: var(--baseFontColor); - font-size: 15px; - font-weight: 400; -} - -h2 img, -.h2 img, -h2 svg, -.h2 svg { - vertical-align: middle; - transform: translateY(-1px); -} - -h3, -.h3 { - line-height: var(--controlHeight); - color: var(--baseFontColor); - font-size: var(--mediumFontSize); - font-weight: 600; -} - -h3 img, -.h3 img, -h3 svg, -.h3 svg { - vertical-align: middle; - transform: translateY(-1px); -} - -h4, -.h4 { - line-height: var(--controlHeight); - color: var(--baseFontColor); - font-size: var(--baseFontSize); - font-weight: 600; -} - -h4 img, -.h4 img, -h4 svg, -.h4 svg { - vertical-align: middle; - transform: translateY(-1px); -} - -h5, -.h5 { - line-height: var(--controlHeight); - color: var(--baseFontColor); - font-size: var(--baseFontSize); - font-weight: 600; -} - -h5 img, -.h5 img, -h5 svg, -.h5 svg { - vertical-align: middle; - transform: translateY(-1px); -} - -h6, -.h6 { - line-height: var(--controlHeight); - color: var(--baseFontColor); - font-size: var(--baseFontSize); - font-weight: 600; -} - -h6 img, -.h6 img, -h6 svg, -.h6 svg { - vertical-align: middle; - transform: translateY(-1px); -} - -em { - font-style: italic; -} - -strong { - font-weight: 600; -} - -.underline { - text-decoration: underline; -} - -mark { - background: none; - color: var(--baseFontColor); - font-weight: bold; -} - -blockquote { - border-left: 3px solid var(--barBorderColor); - padding: 0 8px; - line-height: 1.5; -} - -blockquote cite { - line-height: 1.5; - color: var(--secondFontColor); - font-size: var(--smallFontSize); -} - -small, -.small { - font-size: var(--smallFontSize) !important; -} - -.medium { - font-size: var(--mediumFontSize) !important; -} - -.big { - font-size: var(--bigFontSize) !important; -} - -.huge { - font-size: var(--hugeFontSize) !important; -} - -.gigantic { - font-size: var(--giganticFontSize) !important; -} - -.zero-font-size { - font-size: 0 !important; -} - -.text-left { - text-align: left; -} - -.text-center { - text-align: center; -} - -.text-right { - text-align: right; -} - -.text-justify { - text-align: justify; -} - -.text-top { - vertical-align: top !important; -} - -.text-middle { - vertical-align: middle !important; -} - -.text-bottom { - vertical-align: bottom !important; -} - -.text-text-top { - vertical-align: text-top !important; -} - -.text-text-bottom { - vertical-align: text-bottom !important; -} - -.text-baseline { - vertical-align: baseline !important; -} - -.text-ellipsis { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.text-limited-small { - display: inline-block; - max-width: 8vw; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.text-limited { - display: inline-block; - max-width: 16vw; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.text-uppercase { - text-transform: uppercase; -} - -.text-lowercase { - text-transform: lowercase; -} - -.text-no-transform { - text-transform: none; -} - -.text-light { - font-weight: 300 !important; -} - -.text-normal { - font-weight: normal !important; -} - -.text-bold { - font-weight: bold !important; -} - -.text-muted { - color: var(--secondFontColor); -} - -.text-muted-2 { - color: var(--gray71); -} - -.text-danger { - color: var(--red) !important; -} - -.text-warning { - color: var(--orange) !important; -} - -.text-info { - color: var(--blue) !important; -} - -.text-success { - color: var(--green) !important; -} - -.monospaced { - line-height: 18px; - font-family: var(--sourceCodeFontFamily); - font-size: var(--smallFontSize); -} - -.new-background { - background-color: #fcfcfd; -} - -.white-background { - background-color: #ffffff; -} diff --git a/server/sonar-web/src/main/js/app/styles/mixins.css b/server/sonar-web/src/main/js/app/styles/mixins.css deleted file mode 100644 index ef4eeb2e0f2..00000000000 --- a/server/sonar-web/src/main/js/app/styles/mixins.css +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.clearfix:before, -.clearfix:after { - display: table; - content: ''; - line-height: 0; -} - -.clearfix:after { - clear: both; -} diff --git a/server/sonar-web/src/main/js/app/styles/sonar.ts b/server/sonar-web/src/main/js/app/styles/sonar.ts index fcc97c52a3d..aa6fdccd870 100644 --- a/server/sonar-web/src/main/js/app/styles/sonar.ts +++ b/server/sonar-web/src/main/js/app/styles/sonar.ts @@ -23,27 +23,10 @@ import '../../../../../public/fonts/Inter/inter.css'; import '../../../../../public/fonts/Ubuntu/Ubuntu.css'; -import './components/badges.css'; import './components/boxed-group.css'; -import './components/columns.css'; -import './components/component-name.css'; -import './components/dropdowns.css'; import './components/global-loading.css'; -import './components/issues.css'; -import './components/list-groups.css'; -import './components/menu.css'; import './components/page.css'; -import './components/panels.css'; -import './components/spinner.css'; -import './components/ui.css'; import './init/base.css'; -import './init/forms.css'; -import './init/icons.css'; -import './init/links.css'; -import './init/lists.css'; import './init/misc.css'; -import './init/tables.css'; -import './init/type.css'; -import './mixins.css'; import './print.css'; import './style.css'; diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx index b12aae3de32..553ec297a82 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx @@ -101,6 +101,7 @@ import PortfolioPage from '../components/extensions/PortfolioPage'; import PortfoliosPage from '../components/extensions/PortfoliosPage'; import ProjectAdminPageExtension from '../components/extensions/ProjectAdminPageExtension'; import ProjectPageExtension from '../components/extensions/ProjectPageExtension'; +import { GlobalStyles } from '../styles/GlobalStyles'; import exportModulesAsGlobals from './exportModulesAsGlobals'; function renderComponentRoutes() { @@ -272,6 +273,7 @@ export default function startReactApp( <RawIntlProvider value={l10nBundle}> <ThemeProvider theme={lightTheme}> <QueryClientProvider client={queryClient}> + <GlobalStyles /> <GlobalMessagesContainer /> <Helmet titleTemplate={translate('page_title.template.default')} /> <RouterProvider router={router} /> diff --git a/server/sonar-web/src/main/js/apps/account/projects/Projects.tsx b/server/sonar-web/src/main/js/apps/account/projects/Projects.tsx index 08c88a3cd67..88e849dfa67 100644 --- a/server/sonar-web/src/main/js/apps/account/projects/Projects.tsx +++ b/server/sonar-web/src/main/js/apps/account/projects/Projects.tsx @@ -57,7 +57,6 @@ export default function Projects(props: Readonly<Props>) { loading={props.loading} ready={!props.loading} total={props.total ?? 0} - useMIUIButtons /> </> )} diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx index a340aae00fa..16e02e9b520 100644 --- a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx +++ b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx @@ -274,7 +274,6 @@ export class BackgroundTasksApp extends React.PureComponent<Props, State> { loading={loading} pageSize={pagination.pageSize} total={pagination.total} - useMIUIButtons /> </PageContentFontWrapper> </LargeCenteredLayout> diff --git a/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordAppRenderer.tsx b/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordAppRenderer.tsx index 33db64d2bf7..12e71ce081d 100644 --- a/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordAppRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/change-admin-password/ChangeAdminPasswordAppRenderer.tsx @@ -17,13 +17,23 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { + ButtonPrimary, + Card, + CenteredLayout, + DarkLabel, + FlagMessage, + FormField, + InputField, + Link, + PageContentFontWrapper, + Spinner, + SubTitle, + Title, +} from 'design-system'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; -import { SubmitButton } from '../../components/controls/buttons'; import { Location } from '../../components/hoc/withRouter'; -import { Alert } from '../../components/ui/Alert'; -import MandatoryFieldMarker from '../../components/ui/MandatoryFieldMarker'; -import MandatoryFieldsExplanation from '../../components/ui/MandatoryFieldsExplanation'; import { translate } from '../../helpers/l10n'; import { getReturnUrl } from '../../helpers/urls'; import Unauthorized from '../sessions/components/Unauthorized'; @@ -42,6 +52,9 @@ export interface ChangeAdminPasswordAppRendererProps { location: Location; } +const PASSWORD_FIELD_ID = 'user-password'; +const CONFIRM_PASSWORD_FIELD_ID = 'confirm-user-password'; + export default function ChangeAdminPasswordAppRenderer(props: ChangeAdminPasswordAppRendererProps) { const { canAdmin, @@ -58,93 +71,94 @@ export default function ChangeAdminPasswordAppRenderer(props: ChangeAdminPasswor } return ( - <div className="page-wrapper-simple"> + <CenteredLayout> <Helmet defer={false} title={translate('users.change_admin_password.page')} /> - <div className="page-simple"> - {success ? ( - <Alert variant="success"> - <p className="spacer-bottom">{translate('users.change_admin_password.form.success')}</p> - {/* We must not use Link here, because we need a refresh of the /api/navigation/global call. */} - <a href={getReturnUrl(location)}> - {translate('users.change_admin_password.form.continue_to_app')} - </a> - </Alert> - ) : ( - <> - <h1 className="text-center bg-danger big padded"> - {translate('users.change_admin_password.instance_is_at_risk')} - </h1> - <p className="text-center huge huge-spacer-top"> - {translate('users.change_admin_password.header')} - </p> - <p className="text-center huge-spacer-top huge-spacer-bottom"> - {translate('users.change_admin_password.description')} - </p> - - <form - className="text-center" - onSubmit={(e: React.SyntheticEvent<HTMLFormElement>) => { - e.preventDefault(); - props.onSubmit(); - }} - > - <h2 className="big-spacer-bottom big"> - {translate('users.change_admin_password.form.header')} - </h2> + <PageContentFontWrapper className="sw-body-sm sw-flex sw-flex-col sw-items-center sw-justify-center"> + <Card className="sw-mx-auto sw-mt-24 sw-w-abs-600 sw-flex sw-items-stretch sw-flex-col"> + {success ? ( + <FlagMessage className="sw-my-8" variant="success"> + <div> + <p className="sw-mb-2">{translate('users.change_admin_password.form.success')}</p> + {/* We must reload because we need a refresh of the /api/navigation/global call. */} + <Link to={getReturnUrl(location)} reloadDocument> + {translate('users.change_admin_password.form.continue_to_app')} + </Link> + </div> + </FlagMessage> + ) : ( + <> + <Title>{translate('users.change_admin_password.instance_is_at_risk')}</Title> + <DarkLabel className="sw-mb-2"> + {translate('users.change_admin_password.header')} + </DarkLabel> + <p>{translate('users.change_admin_password.description')}</p> - <MandatoryFieldsExplanation className="form-field" /> + <form + className="sw-mt-8" + onSubmit={(e: React.SyntheticEvent<HTMLFormElement>) => { + e.preventDefault(); + props.onSubmit(); + }} + > + <SubTitle className="sw-mb-4"> + {translate('users.change_admin_password.form.header')} + </SubTitle> - <div className="form-field"> - <label htmlFor="user-password"> - {translate('users.change_admin_password.form.password')} - <MandatoryFieldMarker /> - </label> - <input - id="user-password" - name="password" - onChange={(e: React.SyntheticEvent<HTMLInputElement>) => { - props.onPasswordChange(e.currentTarget.value); - }} + <FormField + label={translate('users.change_admin_password.form.password')} + htmlFor={PASSWORD_FIELD_ID} required - type="password" - value={passwordValue} - /> - </div> + > + <InputField + id={PASSWORD_FIELD_ID} + name="password" + onChange={(e: React.SyntheticEvent<HTMLInputElement>) => { + props.onPasswordChange(e.currentTarget.value); + }} + required + type="password" + value={passwordValue} + /> + </FormField> - <div className="form-field"> - <label htmlFor="confirm-user-password"> - {translate('users.change_admin_password.form.confirm')} - <MandatoryFieldMarker /> - </label> - <input - id="confirm-user-password" - name="confirm-password" - onChange={(e: React.SyntheticEvent<HTMLInputElement>) => { - props.onConfirmPasswordChange(e.currentTarget.value); - }} + <FormField + label={translate('users.change_admin_password.form.confirm')} + htmlFor={CONFIRM_PASSWORD_FIELD_ID} required - type="password" - value={confirmPasswordValue} - /> + description={ + confirmPasswordValue === passwordValue && + passwordValue === DEFAULT_ADMIN_PASSWORD && ( + <FlagMessage className="sw-mt-2" variant="warning"> + {translate('users.change_admin_password.form.cannot_use_default_password')} + </FlagMessage> + ) + } + > + <InputField + id={CONFIRM_PASSWORD_FIELD_ID} + name="confirm-password" + onChange={(e: React.SyntheticEvent<HTMLInputElement>) => { + props.onConfirmPasswordChange(e.currentTarget.value); + }} + required + type="password" + value={confirmPasswordValue} + /> + </FormField> - {confirmPasswordValue === passwordValue && - passwordValue === DEFAULT_ADMIN_PASSWORD && ( - <Alert className="spacer-top" variant="warning"> - {translate('users.change_admin_password.form.cannot_use_default_password')} - </Alert> - )} - </div> - - <div className="form-field"> - <SubmitButton disabled={!canSubmit || submitting}> + <ButtonPrimary + className="sw-mt-8" + disabled={!canSubmit || submitting} + type="submit" + > + <Spinner className="sw-mr-2" loading={submitting} /> {translate('update_verb')} - {submitting && <i className="spinner spacer-left" />} - </SubmitButton> - </div> - </form> - </> - )} - </div> - </div> + </ButtonPrimary> + </form> + </> + )} + </Card> + </PageContentFontWrapper> + </CenteredLayout> ); } diff --git a/server/sonar-web/src/main/js/apps/code/code.css b/server/sonar-web/src/main/js/apps/code/code.css deleted file mode 100644 index 1f981c723fd..00000000000 --- a/server/sonar-web/src/main/js/apps/code/code.css +++ /dev/null @@ -1,143 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.code-components .page-actions { - margin-top: -35px; -} - -.code-components .boxed-group.search-results { - padding-top: 16px; -} - -.code-components .boxed-group.search-results .page-actions { - margin-top: -50px; -} - -.code-components .boxed-group.search-results li { - padding: var(--gridSize) calc(2 * var(--gridSize)); - border-width: 0 0 0 2px; - border-style: solid; - border-color: transparent; -} - -.code-components .boxed-group.search-results li.selected { - background-color: var(--info100); - border-left-color: var(--info500); -} - -.code-components .table-wrapper { - margin: 0 20px; -} - -.code-components table.data { - table-layout: fixed; -} - -.code-components table.data td { - padding: 8px 6px; - vertical-align: middle; -} - -.code-components table.data th { - padding-top: 24px; -} - -.code-components table.data th, -.code-components table.data td:not(.thin) { - width: 84px; -} - -.code-components table.data td.code-name-cell, -.code-components table.data th.code-name-cell { - width: auto; -} - -.code-components table.data th.thin, -.code-components table.data td.thin { - width: 10px !important; -} - -.code-components table.data tr.current-folder { - border-bottom: 1px solid var(--barBorderColor); -} - -.code-components table.data tr.current-folder td { - padding-bottom: 16px !important; - padding-top: 10px !important; -} - -.code-breadcrumbs { - display: flex; - flex-wrap: wrap; -} - -.code-breadcrumbs > li { - padding: 5px 5px 3px; - display: flex; -} - -.code-breadcrumbs > li:first-child { - padding-left: 0; -} - -.code-breadcrumbs > li::after { - position: relative; - top: 1px; - padding-left: 10px; - color: var(--secondFontColor); - font-size: 11px; - content: '>'; -} - -.code-breadcrumbs > li:last-child::after { - display: none; -} - -@media (max-width: 1200px) { - .code-name-cell .badge { - display: none; - } -} - -.code-components-header { - position: sticky; - top: 105px; - background-color: rgba(255, 255, 255, 0.9); - z-index: 1; -} - -table > thead > tr.code-components-header > th { - vertical-align: middle; -} - -.code-child-component-icon { - display: inline-block; - border-left: 1px solid var(--secondFontColor); - border-bottom: 1px solid var(--secondFontColor); - margin-left: 8px; - margin-bottom: 8px; - margin-right: 4px; - height: 8px; - width: 4px; -} - -.code-components .no-file .h1 { - position: fixed; - top: 50%; -} diff --git a/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx b/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx index 15fe0292fc6..9773a641bb9 100644 --- a/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx @@ -26,7 +26,6 @@ import { WithBranchLikesProps, useBranchesQuery } from '../../../queries/branch' import { ComponentQualifier, isPortfolioLike } from '../../../types/component'; import { Breadcrumb, Component, ComponentMeasure, Dict, Metric } from '../../../types/types'; import { addComponent, addComponentBreadcrumbs, clearBucket } from '../bucket'; -import '../code.css'; import { loadMoreChildren, retrieveComponent, retrieveComponentChildren } from '../utils'; import CodeAppRenderer from './CodeAppRenderer'; diff --git a/server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx b/server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx index 715adcae902..ea6dca7d68b 100644 --- a/server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/CodeAppRenderer.tsx @@ -39,7 +39,6 @@ import { translate } from '../../../helpers/l10n'; import { BranchLike } from '../../../types/branch-like'; import { isApplication, isPortfolioLike } from '../../../types/component'; import { Breadcrumb, Component, ComponentMeasure, Dict, Metric } from '../../../types/types'; -import '../code.css'; import { getCodeMetrics } from '../utils'; import CodeBreadcrumbs from './CodeBreadcrumbs'; import Components from './Components'; diff --git a/server/sonar-web/src/main/js/apps/code/components/Components.tsx b/server/sonar-web/src/main/js/apps/code/components/Components.tsx index d0914e973a7..a604644e209 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Components.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/Components.tsx @@ -63,7 +63,7 @@ function Components(props: ComponentsProps) { const columnCount = metrics.length + Number(canBePinned) + Number(showAnalysisDate) + 1; return ( - <div className="big-spacer-bottom table-wrapper"> + <div className="sw-mb-4"> <Table columnCount={columnCount} columnWidths={[ diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx index 508eefcceb3..087d44a1086 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx @@ -30,7 +30,7 @@ import { } from 'design-system'; import * as React from 'react'; import { Profile } from '../../../api/quality-profiles'; -import DocLink from '../../../components/common/DocLink'; +import DocumentationLink from '../../../components/common/DocumentationLink'; import { translate } from '../../../helpers/l10n'; import { sanitizeString } from '../../../helpers/sanitize'; import { useActivateRuleMutation } from '../../../queries/quality-profiles'; @@ -117,12 +117,12 @@ export default function ActivationFormModal(props: Readonly<Props>) { <FlagMessage className="sw-mb-4" variant="info"> {translate('coding_rules.severity_deprecated')} - <DocLink + <DocumentationLink className="sw-ml-2 sw-whitespace-nowrap" to="/user-guide/clean-code/introduction/" > {translate('learn_more')} - </DocLink> + </DocumentationLink> </FlagMessage> <FormField diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx index 69664400d7d..01a7b24531d 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx @@ -690,7 +690,6 @@ export class CodingRulesApp extends React.PureComponent<Props, State> { loadMore={this.fetchMoreRules} ready={!this.state.loading} total={paging.total} - useMIUIButtons /> )} </> diff --git a/server/sonar-web/src/main/js/apps/component-measures/style.css b/server/sonar-web/src/main/js/apps/component-measures/style.css index c7f9177c00c..d9177fc0aa3 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/style.css +++ b/server/sonar-web/src/main/js/apps/component-measures/style.css @@ -21,34 +21,6 @@ button.search-navigator-facet { text-align: start; } -.search-navigator-facet .leak-box { - height: var(--controlHeight); - line-height: var(--controlHeight); - padding: 0 var(--gridSize); - margin-top: -1px; - margin-right: calc(-0.75 * var(--gridSize) - 1px); - border-radius: 2px; - box-sizing: border-box; -} - -.search-navigator-facet:hover .leak-box, -.search-navigator-facet.active .leak-box { - height: calc(var(--controlHeight) - 2px); - margin-top: 0; - margin-right: calc(-0.75 * var(--gridSize)); - border-top: none; - border-bottom: none; - border-right: none; - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.search-navigator-facet.active .leak-box { - border-left: none; - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - .measure-details-treemap-legend.color-box-legend { margin-right: 0; } diff --git a/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectAccordion.tsx b/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectAccordion.tsx index 42e0bf88c3e..72ebbb98a26 100644 --- a/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectAccordion.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectAccordion.tsx @@ -64,7 +64,7 @@ export default function AzureProjectAccordion(props: AzureProjectAccordionProps) </span> } > - {/* eslint-disable-next-line local-rules/no-conditional-rendering-of-deferredspinner*/} + {/* eslint-disable-next-line local-rules/no-conditional-rendering-of-spinner*/} {open && ( <Spinner loading={loading}> {repositories.length === 0 ? ( @@ -113,7 +113,6 @@ export default function AzureProjectAccordion(props: AzureProjectAccordionProps) count={limitedRepositories.length} total={repositories.length} loadMore={() => setPage((p) => p + 1)} - useMIUIButtons /> </> )} diff --git a/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectsList.tsx b/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectsList.tsx index 70c5d97da40..8bb9844a973 100644 --- a/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectsList.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectsList.tsx @@ -131,7 +131,6 @@ export default function AzureProjectsList(props: AzureProjectsListProps) { count={displayedProjects.length} loadMore={() => setPage((p) => p + 1)} total={filteredProjects.length} - useMIUIButtons /> </div> ); diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudSearchForm.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudSearchForm.tsx index 1b1c60f2238..2b3fcd5c70e 100644 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudSearchForm.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudSearchForm.tsx @@ -115,7 +115,6 @@ export default function BitbucketCloudSearchForm(props: BitbucketCloudSearchForm pageSize={BITBUCKET_CLOUD_PROJECTS_PAGESIZE} loadMore={props.onLoadMore} loading={loadingMore} - useMIUIButtons /> </div> ); diff --git a/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreateRenderer.tsx b/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreateRenderer.tsx index e8bf697c7ab..9abb7f27e35 100644 --- a/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreateRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreateRenderer.tsx @@ -37,8 +37,8 @@ import { import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; import ListFooter from '../../../../components/controls/ListFooter'; -import { LabelValueSelectOption } from '../../../../components/controls/Select'; import { translate } from '../../../../helpers/l10n'; +import { LabelValueSelectOption } from '../../../../helpers/search'; import { getBaseUrl } from '../../../../helpers/system'; import { GithubOrganization, GithubRepository } from '../../../../types/alm-integration'; import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings'; @@ -167,7 +167,6 @@ function RepositoryList(props: RepositoryListProps) { total={repositoryPaging.total} loadMore={props.onLoadMore} loading={loadingRepositories} - useMIUIButtons /> </div> ); diff --git a/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectSelectionForm.tsx b/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectSelectionForm.tsx index c9f8f472d23..7d83e80aaa3 100644 --- a/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectSelectionForm.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectSelectionForm.tsx @@ -117,7 +117,6 @@ export default function GitlabProjectSelectionForm(props: GitlabProjectSelection loading={loadingMore} pageSize={projectsPaging.pageSize} total={projectsPaging.total} - useMIUIButtons /> </> ); diff --git a/server/sonar-web/src/main/js/apps/groups/GroupsApp.tsx b/server/sonar-web/src/main/js/apps/groups/GroupsApp.tsx index 7a37e33d652..952985e7ece 100644 --- a/server/sonar-web/src/main/js/apps/groups/GroupsApp.tsx +++ b/server/sonar-web/src/main/js/apps/groups/GroupsApp.tsx @@ -79,7 +79,6 @@ export default function GroupsApp() { loadMore={fetchNextPage} ready={!isLoading} total={data?.pages[0].page.total} - useMIUIButtons /> </main> </PageContentFontWrapper> diff --git a/server/sonar-web/src/main/js/apps/groups/components/ViewMembersModal.tsx b/server/sonar-web/src/main/js/apps/groups/components/ViewMembersModal.tsx index 742251d696e..e124d8bf546 100644 --- a/server/sonar-web/src/main/js/apps/groups/components/ViewMembersModal.tsx +++ b/server/sonar-web/src/main/js/apps/groups/components/ViewMembersModal.tsx @@ -78,7 +78,6 @@ export default function ViewMembersModal(props: Readonly<Props>) { count={users.length} loadMore={fetchNextPage} total={data?.pages[0].page.total} - useMIUIButtons /> )} </div> diff --git a/server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx b/server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx index 73663f19e71..926327f5f6f 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/ComponentBreadcrumbs.tsx @@ -18,9 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import styled from '@emotion/styled'; -import { Badge, themeBorder, themeContrast } from 'design-system'; +import { Badge, BranchIcon, themeBorder, themeContrast } from 'design-system'; import * as React from 'react'; -import BranchIcon from '../../../components/icons/BranchIcon'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { collapsePath, limitComponentName } from '../../../helpers/path'; import { ComponentQualifier, isView } from '../../../types/component'; diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueGuide.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueGuide.tsx index fa814f50376..32e94ac3f4b 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssueGuide.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssueGuide.tsx @@ -23,7 +23,7 @@ import { FormattedMessage } from 'react-intl'; import { CallBackProps } from 'react-joyride'; import { dismissNotice } from '../../../api/users'; import { CurrentUserContext } from '../../../app/components/current-user/CurrentUserContext'; -import DocLink from '../../../components/common/DocLink'; +import DocumentationLink from '../../../components/common/DocumentationLink'; import { SCREEN_POSITION_COMPUTE_DELAY } from '../../../components/common/ScreenPositionHelper'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { NoticeType } from '../../../types/users'; @@ -153,9 +153,9 @@ export default function IssueGuide({ run }: Props) { defaultMessage={translate('guiding.issue_list.5.content')} values={{ link: ( - <DocLink to="/user-guide/clean-code/introduction" className="sw-capitalize"> + <DocumentationLink to="/user-guide/clean-code/introduction" className="sw-capitalize"> {translate('documentation')} - </DocLink> + </DocumentationLink> ), }} /> diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueReviewHistory.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueReviewHistory.tsx index 9bdc3e17d9b..36b99e6805b 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssueReviewHistory.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssueReviewHistory.tsx @@ -33,7 +33,7 @@ import * as React from 'react'; import { getIssueChangelog } from '../../../api/issues'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import IssueChangelogDiff from '../../../components/issue/components/IssueChangelogDiff'; -import LegacyAvatar from '../../../components/ui/LegacyAvatar'; +import Avatar from '../../../components/ui/Avatar'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { sanitizeUserInput } from '../../../helpers/sanitize'; import { ReviewHistoryType } from '../../../types/security-hotspots'; @@ -88,7 +88,7 @@ export default function IssueReviewHistory(props: HotspotReviewHistoryProps) { <LightLabel as="div" className="sw-flex sw-gap-2"> {user.name && ( <div className="sw-flex sw-items-center sw-gap-1"> - <LegacyAvatar hash={user.avatar} name={user.name} size={20} /> + <Avatar hash={user.avatar} name={user.name} size="xs" /> <span className="sw-body-sm-highlight"> {user.active ? user.name : translateWithParameters('user.x_deleted', user.name)} </span> diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx index f712711fcd7..f793b5bc2b6 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx @@ -1089,10 +1089,10 @@ export class App extends React.PureComponent<Props, State> { <SideBarStyle> <ScreenPositionHelper className="sw-z-filterbar"> {({ top }) => ( - <nav + <StyledNav aria-label={openIssue ? translate('list_of_issues') : translate('filters')} data-testid="issues-nav-bar" - className="issues-nav-bar sw-overflow-y-auto issue-filters-list" + className="issues-nav-bar sw-overflow-y-auto" style={{ height: `calc((100vh - ${top}px) - ${LAYOUT_FOOTER_HEIGHT}px)` }} > <div className="sw-w-[300px] lg:sw-w-[390px] sw-h-full"> @@ -1128,7 +1128,7 @@ export class App extends React.PureComponent<Props, State> { this.renderFacets(warning) )} </div> - </nav> + </StyledNav> )} </ScreenPositionHelper> </SideBarStyle> @@ -1189,7 +1189,6 @@ export class App extends React.PureComponent<Props, State> { loading={loadingMore} pageSize={ISSUES_PAGE_SIZE} total={paging.total} - useMIUIButtons /> )} @@ -1393,3 +1392,13 @@ const StyledIssueWrapper = styled.div` border-top: none; } `; + +const StyledNav = styled.nav` + /* +* On Firefox on Windows, the scrollbar hides the sidebar's content. +* Using 'scrollbar-gutter:stable' is a workaround to ensure consistency with other browsers. +* @see https://bugzilla.mozilla.org/show_bug.cgi?id=764076 +* @see https://discuss.sonarsource.com/t/unnecessary-horizontal-scrollbar-on-issues-page/14889/4 +*/ + scrollbar-gutter: stable; +`; diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx index ce4845a8874..840cc4a65b0 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx @@ -17,12 +17,12 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Spinner } from 'design-system'; import { groupBy } from 'lodash'; import * as React from 'react'; import IssueItem from '../../../components/issue/Issue'; import { BranchLike } from '../../../types/branch-like'; import { Component, Issue } from '../../../types/types'; - import ComponentBreadcrumbs from './ComponentBreadcrumbs'; interface Props { @@ -93,7 +93,7 @@ export default class IssuesList extends React.PureComponent<Props, State> { if (prerender) { return ( <div> - <i className="spinner" /> + <Spinner /> </div> ); } diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewer.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewer.tsx index 2369d3edba5..ff0e0c20fd1 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewer.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewer.tsx @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Spinner } from 'design-system'; +import { FlagMessage, Spinner } from 'design-system'; import { findLastIndex, keyBy } from 'lodash'; import * as React from 'react'; import { getComponentForSourceViewer, getDuplications, getSources } from '../../../api/components'; @@ -33,7 +33,6 @@ import { duplicationsByLine as getDuplicationsByLine, issuesByComponentAndLine, } from '../../../components/SourceViewer/helpers/indexing'; -import { Alert } from '../../../components/ui/Alert'; import { WorkspaceContext } from '../../../components/workspace/context'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { throwGlobalError } from '../../../helpers/error'; @@ -199,9 +198,9 @@ export default class CrossComponentSourceViewer extends React.PureComponent<Prop if (notAccessible) { return ( - <Alert className="spacer-top" variant="warning"> + <FlagMessage className="sw-mt-2" variant="warning"> {translate('code_viewer.no_source_code_displayed_due_to_security')} - </Alert> + </FlagMessage> ); } diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx index a2391ff6f2a..9b2b3c398bb 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx @@ -22,7 +22,7 @@ import styled from '@emotion/styled'; import classNames from 'classnames'; import { ChevronRightIcon, - CopyIcon, + ClipboardIconButton, HoverLink, InteractiveIcon, LightLabel, @@ -34,8 +34,6 @@ import { import * as React from 'react'; import { ComponentContext } from '../../../app/components/componentContext/ComponentContext'; import { useCurrentUser } from '../../../app/components/current-user/CurrentUserContext'; -import Tooltip from '../../../components/controls/Tooltip'; -import { ClipboardBase } from '../../../components/controls/clipboard'; import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils'; import { getBranchLikeQuery, isBranch, isPullRequest } from '../../../helpers/branch-like'; import { translate } from '../../../helpers/l10n'; @@ -139,30 +137,11 @@ export function IssueSourceViewerHeader(props: Readonly<Props>) { {collapsedDirFromPath(path)} {fileFromPath(path)} </LightLabel> - - <ClipboardBase> - {({ setCopyButton, copySuccess }) => { - return ( - <Tooltip - mouseEnterDelay={INTERACTIVE_TOOLTIP_DELAY} - overlay={ - <div className="sw-w-abs-150 sw-text-center"> - {translate(copySuccess ? 'copied_action' : 'copy_to_clipboard')} - </div> - } - {...(copySuccess ? { visible: copySuccess } : undefined)} - > - <InteractiveIcon - Icon={CopyIcon} - aria-label={translate('source_viewer.click_to_copy_filepath')} - data-clipboard-text={path} - className="sw-h-6 sw-mx-2" - innerRef={setCopyButton} - /> - </Tooltip> - ); - }} - </ClipboardBase> + <ClipboardIconButton + className="sw-h-6 sw-mx-2" + copyValue={path} + copyLabel={translate('source_viewer.click_to_copy_filepath')} + /> </span> )} </div> diff --git a/server/sonar-web/src/main/js/apps/issues/issues-subnavigation/SubnavigationIssuesList.tsx b/server/sonar-web/src/main/js/apps/issues/issues-subnavigation/SubnavigationIssuesList.tsx index 535026d0c36..8d989f2d7f3 100644 --- a/server/sonar-web/src/main/js/apps/issues/issues-subnavigation/SubnavigationIssuesList.tsx +++ b/server/sonar-web/src/main/js/apps/issues/issues-subnavigation/SubnavigationIssuesList.tsx @@ -88,7 +88,6 @@ export default function SubnavigationIssuesList(props: Props) { loadMore={props.fetchMoreIssues} loading={loadingMore} total={paging.total} - useMIUIButtons /> )} </StyledWrapper> diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/ListStyleFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/ListStyleFacet.tsx index 4ccc67d78ef..0435087a152 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/ListStyleFacet.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/ListStyleFacet.tsx @@ -420,7 +420,6 @@ export class ListStyleFacet<S> extends React.Component<Props<S>, State<S>> { loadMoreAriaLabel={showMoreAriaLabel} ready={!searching} total={searchPaging.total} - useMIUIButtons /> )} </> diff --git a/server/sonar-web/src/main/js/apps/issues/styles.css b/server/sonar-web/src/main/js/apps/issues/styles.css index c16ff80ecf6..a728b86e641 100644 --- a/server/sonar-web/src/main/js/apps/issues/styles.css +++ b/server/sonar-web/src/main/js/apps/issues/styles.css @@ -17,14 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -.issues-main-header .component-name { - line-height: var(--controlHeight); -} - -.issues-main-header-spinner { - margin-right: 2px; -} - .not-all-issue-warning.open-issue-list { background-color: var(--barBackgroundColor); box-sizing: border-box; diff --git a/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx b/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx index c1f776c0154..03f90e84fe0 100644 --- a/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/maintenance/components/App.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import styled from '@emotion/styled'; -import { ButtonPrimary, Card, Link, Note, Spinner, Title } from 'design-system'; +import { ButtonPrimary, Card, CenteredLayout, Link, Note, Spinner, Title } from 'design-system'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; import { FormattedMessage } from 'react-intl'; @@ -137,7 +137,7 @@ export default class App extends React.PureComponent<Props, State> { return ( <> <Helmet defaultTitle={translate('maintenance.page')} defer={false} /> - <div className="page-wrapper-simple" id="bd"> + <CenteredLayout className="sw-flex sw-justify-around sw-mt-32" id="bd"> <Card className="sw-body-sm sw-p-10 sw-w-abs-400" id="nonav"> {status === 'OFFLINE' && ( <> @@ -307,7 +307,7 @@ export default class App extends React.PureComponent<Props, State> { </> )} </Card> - </div> + </CenteredLayout> </> ); } diff --git a/server/sonar-web/src/main/js/apps/marketplace/App.tsx b/server/sonar-web/src/main/js/apps/marketplace/App.tsx index 3ed9b3b018b..c122f26873a 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/App.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/App.tsx @@ -220,11 +220,7 @@ class App extends React.PureComponent<Props, State> { readOnly={!allowActions} refreshPending={this.props.fetchPendingPlugins} /> - <ListFooter - useMIUIButtons - count={filteredPlugins.length} - total={plugins.length} - /> + <ListFooter count={filteredPlugins.length} total={plugins.length} /> </> )} </Spinner> diff --git a/server/sonar-web/src/main/js/apps/overview/branches/FirstAnalysisNextStepsNotif.tsx b/server/sonar-web/src/main/js/apps/overview/branches/FirstAnalysisNextStepsNotif.tsx index 57ec902f31c..88737d1605f 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/FirstAnalysisNextStepsNotif.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/FirstAnalysisNextStepsNotif.tsx @@ -17,10 +17,10 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Link } from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext'; -import Link from '../../../components/common/Link'; import DismissableAlert from '../../../components/ui/DismissableAlert'; import { translate } from '../../../helpers/l10n'; import { queryToSearch } from '../../../helpers/urls'; @@ -84,46 +84,48 @@ export function FirstAnalysisNextStepsNotif(props: FirstAnalysisNextStepsNotifPr return ( <DismissableAlert alertKey={`config_ci_pr_deco.${component.key}`} variant="info"> - {showOnlyConfigureCI && ( - <FormattedMessage - defaultMessage={translate('overview.project.next_steps.set_up_ci')} - id="overview.project.next_steps.set_up_ci" - values={{ - link: tutorialsLink, - }} - /> - )} - - {showOnlyConfigurePR && - (isProjectAdmin ? ( + <div> + {showOnlyConfigureCI && ( <FormattedMessage - defaultMessage={translate('overview.project.next_steps.set_up_pr_deco.admin')} - id="overview.project.next_steps.set_up_pr_deco.admin" + defaultMessage={translate('overview.project.next_steps.set_up_ci')} + id="overview.project.next_steps.set_up_ci" values={{ - link_project_settings: projectSettingsLink, + link: tutorialsLink, }} /> - ) : ( - translate('overview.project.next_steps.set_up_pr_deco') - ))} + )} - {showBoth && - (isProjectAdmin ? ( - <FormattedMessage - defaultMessage={translate('overview.project.next_steps.set_up_pr_deco_and_ci.admin')} - id="overview.project.next_steps.set_up_pr_deco_and_ci.admin" - values={{ - link_ci: tutorialsLink, - link_project_settings: projectSettingsLink, - }} - /> - ) : ( - <FormattedMessage - defaultMessage={translate('overview.project.next_steps.set_up_pr_deco_and_ci')} - id="overview.project.next_steps.set_up_pr_deco_and_ci" - values={{ link_ci: tutorialsLink }} - /> - ))} + {showOnlyConfigurePR && + (isProjectAdmin ? ( + <FormattedMessage + defaultMessage={translate('overview.project.next_steps.set_up_pr_deco.admin')} + id="overview.project.next_steps.set_up_pr_deco.admin" + values={{ + link_project_settings: projectSettingsLink, + }} + /> + ) : ( + translate('overview.project.next_steps.set_up_pr_deco') + ))} + + {showBoth && + (isProjectAdmin ? ( + <FormattedMessage + defaultMessage={translate('overview.project.next_steps.set_up_pr_deco_and_ci.admin')} + id="overview.project.next_steps.set_up_pr_deco_and_ci.admin" + values={{ + link_ci: tutorialsLink, + link_project_settings: projectSettingsLink, + }} + /> + ) : ( + <FormattedMessage + defaultMessage={translate('overview.project.next_steps.set_up_pr_deco_and_ci')} + id="overview.project.next_steps.set_up_pr_deco_and_ci" + values={{ link_ci: tutorialsLink }} + /> + ))} + </div> </DismissableAlert> ); } diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx index 17bc57ee8fc..cc50083316b 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx @@ -17,11 +17,10 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Link, Note, getTabPanelId } from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import DocLink from '../../../components/common/DocLink'; -import Link from '../../../components/common/Link'; -import { getTabPanelId } from '../../../components/controls/BoxedTabs'; +import DocumentationLink from '../../../components/common/DocumentationLink'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { translate } from '../../../helpers/l10n'; import { getBaseUrl } from '../../../helpers/system'; @@ -62,18 +61,18 @@ export default function MeasuresPanelNoNewCode(props: MeasuresPanelNoNewCodeProp return ( <div - className="display-flex-center display-flex-justify-center" + className="sw-flex sw-items-center sw-justify-center" id={getTabPanelId(CodeScope.New)} style={{ height: 500 }} > <img alt="" /* Make screen readers ignore this image; it's purely eye candy. */ - className="spacer-right" + className="sw-mr-2" height={52} src={`${getBaseUrl()}/images/source-code.svg`} /> - <div className="big-spacer-left text-muted" style={{ maxWidth: 500 }}> - <p className="spacer-bottom big-spacer-top big">{translate(badExplanationKey)}</p> + <Note as="div" className="sw-ml-4 sw-max-w-abs-500"> + <p className="sw-mb-2 sw-mt-4">{translate(badExplanationKey)}</p> {hasBadNewCodeSettingSameRef ? ( showSettingsLink && ( <p> @@ -102,13 +101,15 @@ export default function MeasuresPanelNoNewCode(props: MeasuresPanelNoNewCodeProp id="overview.measures.empty_link" values={{ learn_more_link: ( - <DocLink to="/user-guide/clean-as-you-code/">{translate('learn_more')}</DocLink> + <DocumentationLink to="/user-guide/clean-as-you-code/"> + {translate('learn_more')} + </DocumentationLink> ), }} /> </p> )} - </div> + </Note> </div> ); } diff --git a/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx index 20c966c44f5..48372174414 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/NewCodeMeasuresPanel.tsx @@ -27,11 +27,11 @@ import { TextError, TextSubdued, TrendUpCircleIcon, + getTabPanelId, themeColor, } from 'design-system'; import React from 'react'; import { useIntl } from 'react-intl'; -import { getTabPanelId } from '../../../components/controls/BoxedTabs'; import { getLeakValue } from '../../../components/measure/utils'; import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/TabsPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/TabsPanel.tsx index d4f366e35e1..489860ba459 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/TabsPanel.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/TabsPanel.tsx @@ -29,7 +29,7 @@ import { } from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import DocLink from '../../../components/common/DocLink'; +import DocumentationLink from '../../../components/common/DocumentationLink'; import { translate } from '../../../helpers/l10n'; import { isDiffMetric } from '../../../helpers/measures'; import { CodeScope } from '../../../helpers/urls'; @@ -182,12 +182,12 @@ export function TabsPanel(props: React.PropsWithChildren<MeasuresPanelProps>) { {`${translate('indexation.in_progress')} ${translate( 'indexation.details_unavailable', )}`} - <DocLink + <DocumentationLink className="sw-ml-1 sw-whitespace-nowrap" to="/instance-administration/reindexing/" > {translate('learn_more')} - </DocLink> + </DocumentationLink> </span> </FlagMessage> )} diff --git a/server/sonar-web/src/main/js/apps/overview/components/AnalysisErrorModal.tsx b/server/sonar-web/src/main/js/apps/overview/components/AnalysisErrorModal.tsx index 3158d29fc17..933c04c4924 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/AnalysisErrorModal.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/AnalysisErrorModal.tsx @@ -17,9 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +import { Modal } from 'design-system'; import * as React from 'react'; -import Modal from '../../../components/controls/Modal'; -import { ResetButtonLink } from '../../../components/controls/buttons'; import { hasMessage, translate } from '../../../helpers/l10n'; import { Task } from '../../../types/tasks'; import { Component } from '../../../types/types'; @@ -32,36 +32,26 @@ interface Props { onClose: () => void; } -export function AnalysisErrorModal(props: Props) { - const { component, currentTask } = props; +export function AnalysisErrorModal(props: Readonly<Props>) { + const { component, currentTask, onClose } = props; const header = translate('error'); const licenseError = - currentTask.errorType && + currentTask.errorType !== undefined && hasMessage('license.component_navigation.button', currentTask.errorType); return ( - <Modal contentLabel={header} onRequestClose={props.onClose}> - <header className="modal-head"> - <h2>{header}</h2> - </header> - - <div className="modal-body modal-container"> - {licenseError ? ( + <Modal + body={ + licenseError ? ( <AnalysisLicenseError currentTask={currentTask} /> ) : ( - <AnalysisErrorMessage - component={component} - currentTask={currentTask} - onLeave={props.onClose} - /> - )} - </div> - - <footer className="modal-foot"> - <ResetButtonLink onClick={props.onClose}>{translate('close')}</ResetButtonLink> - </footer> - </Modal> + <AnalysisErrorMessage component={component} currentTask={currentTask} onLeave={onClose} /> + ) + } + headerTitle={header} + onClose={onClose} + /> ); } diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewDisabledLinkTooltip.tsx b/server/sonar-web/src/main/js/apps/overview/components/OverviewDisabledLinkTooltip.tsx index 630371452bb..7cc7dd68d74 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/OverviewDisabledLinkTooltip.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/OverviewDisabledLinkTooltip.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import DocLink from '../../../components/common/DocLink'; +import DocumentationLink from '../../../components/common/DocumentationLink'; import { translate } from '../../../helpers/l10n'; export function OverviewDisabledLinkTooltip() { @@ -34,9 +34,9 @@ export function OverviewDisabledLinkTooltip() { <span className="sw-body-sm-highlight">{translate('indexation.learn_more')}</span> - <DocLink className="sw-ml-1" to="/instance-administration/reindexing/"> + <DocumentationLink className="sw-ml-1" to="/instance-administration/reindexing/"> {translate('indexation.reindexing')} - </DocLink> + </DocumentationLink> </div> ); } diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/AnalysisErrorModal-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/AnalysisErrorModal-test.tsx new file mode 100644 index 00000000000..eaeb834eaf5 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/AnalysisErrorModal-test.tsx @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { screen } from '@testing-library/react'; +import * as React from 'react'; +import { mockComponent } from '../../../../helpers/mocks/component'; +import { mockTask } from '../../../../helpers/mocks/tasks'; +import { renderApp } from '../../../../helpers/testReactTestingUtils'; +import { AnalysisErrorModal } from '../AnalysisErrorModal'; + +jest.mock('../AnalysisErrorMessage', () => ({ + AnalysisErrorMessage: () => <div>analysis error message</div>, +})); + +jest.mock('../AnalysisLicenseError', () => ({ + AnalysisLicenseError: () => <div>analysis license error</div>, +})); + +it('should show the license error message', () => { + renderAnalysisErrorModal({ + currentTask: mockTask({ errorType: 'ANY_TYPE' }), + }); + + expect(screen.getByText('error')).toBeInTheDocument(); + expect(screen.queryByText('analysis error message')).not.toBeInTheDocument(); + expect(screen.getByText('analysis license error')).toBeInTheDocument(); +}); + +it('should show the analysis error message', () => { + renderAnalysisErrorModal(); + + expect(screen.getByText('error')).toBeInTheDocument(); + expect(screen.queryByText('analysis license error')).not.toBeInTheDocument(); + expect(screen.getByText('analysis error message')).toBeInTheDocument(); +}); + +function renderAnalysisErrorModal( + overrides: Partial<Parameters<typeof AnalysisErrorModal>[0]> = {}, + location = '/', +) { + return renderApp( + location, + <AnalysisErrorModal + component={mockComponent()} + currentTask={mockTask()} + onClose={jest.fn()} + {...overrides} + />, + ); +} diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeTabs.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeTabs.tsx index 62fe2d8dec5..acd2b984c92 100644 --- a/server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeTabs.tsx +++ b/server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeTabs.tsx @@ -17,10 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { BranchIcon, PullRequestIcon, ToggleButton } from 'design-system'; +import { BranchIcon, PullRequestIcon, ToggleButton, getTabId, getTabPanelId } from 'design-system'; import * as React from 'react'; import { useState } from 'react'; -import { getTabId, getTabPanelId } from '../../../components/controls/BoxedTabs'; import { isBranch, isMainBranch, diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/AboutProject.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/AboutProject.tsx index 0f2d3a3e87d..086ad5b782a 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/AboutProject.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/AboutProject.tsx @@ -62,14 +62,11 @@ export default function AboutProject(props: AboutProjectProps) { {!isApp && (component.qualityGate || (component.qualityProfiles && component.qualityProfiles.length > 0)) && ( - <ProjectInformationSection className="sw-pt-0"> + <ProjectInformationSection className="sw-pt-0 sw-flex sw-flex-col sw-gap-4"> {component.qualityGate && <MetaQualityGate qualityGate={component.qualityGate} />} {component.qualityProfiles && component.qualityProfiles.length > 0 && ( - <MetaQualityProfiles - headerClassName={component.qualityGate ? 'big-spacer-top' : undefined} - profiles={component.qualityProfiles} - /> + <MetaQualityProfiles profiles={component.qualityProfiles} /> )} </ProjectInformationSection> )} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaDescription.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaDescription.tsx index 158ebb65466..1aaab5d3555 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaDescription.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaDescription.tsx @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { TextMuted } from 'design-system'; +import { SubHeading, TextMuted } from 'design-system'; import * as React from 'react'; import { translate } from '../../../../helpers/l10n'; @@ -29,7 +29,7 @@ interface Props { export default function MetaDescription({ description, isApp }: Props) { return ( <> - <h3>{translate('project.info.description')}</h3> + <SubHeading>{translate('project.info.description')}</SubHeading> <TextMuted className="it__project-description" text={description ?? translate(isApp ? 'application' : 'project', 'info.empty_description')} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaKey.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaKey.tsx index 8c8adcab655..1aefa601e49 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaKey.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaKey.tsx @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { ClipboardIconButton, CodeSnippet, HelperHintIcon } from 'design-system'; +import { ClipboardIconButton, CodeSnippet, HelperHintIcon, SubHeading } from 'design-system'; import * as React from 'react'; import HelpTooltip from '../../../../components/controls/HelpTooltip'; import { translate } from '../../../../helpers/l10n'; @@ -30,8 +30,8 @@ interface MetaKeyProps { export default function MetaKey({ componentKey, qualifier }: MetaKeyProps) { return ( <> - <div className="sw-flex sw-items-center"> - <h3>{translate('overview.project_key', qualifier)}</h3> + <div className="sw-flex sw-items-baseline"> + <SubHeading>{translate('overview.project_key', qualifier)}</SubHeading> <HelpTooltip className="sw-ml-1" overlay={ diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaLinks.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaLinks.tsx index 8891c5311cc..bc1e8936ad4 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaLinks.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaLinks.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { SubHeading } from 'design-system/lib'; import React from 'react'; import MetaLink from '../../../../components/common/MetaLink'; import { translate } from '../../../../helpers/l10n'; @@ -32,8 +33,8 @@ export default function MetaLinks({ links }: Readonly<Props>) { return ( <> - <h3 id="external-links">{translate('overview.external_links')}</h3> - <ul className="project-info-list" aria-labelledby="external-links"> + <SubHeading id="external-links">{translate('overview.external_links')}</SubHeading> + <ul className="sw-flex sw-flex-col sw-gap-2" aria-labelledby="external-links"> {orderedLinks.map((link) => ( <MetaLink key={link.id} link={link} /> ))} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityGate.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityGate.tsx index 94b77af304b..ada3ea85a58 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityGate.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityGate.tsx @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Link } from 'design-system'; +import { Link, Note, SubHeading } from 'design-system'; import * as React from 'react'; import { translate } from '../../../../helpers/l10n'; import { getQualityGateUrl } from '../../../../helpers/urls'; @@ -28,17 +28,15 @@ interface Props { export default function MetaQualityGate({ qualityGate }: Props) { return ( - <> - <h3 id="quality-gate-header">{translate('project.info.quality_gate')}</h3> + <div> + <SubHeading id="quality-gate-header">{translate('project.info.quality_gate')}</SubHeading> - <ul className="project-info-list" aria-labelledby="quality-gate-header"> + <ul className="sw-flex sw-flex-col sw-gap-2" aria-labelledby="quality-gate-header"> <li> - {qualityGate.isDefault && ( - <span className="note spacer-right">({translate('default')})</span> - )} + {qualityGate.isDefault && <Note className="sw-mr-2">({translate('default')})</Note>} <Link to={getQualityGateUrl(qualityGate.name)}>{qualityGate.name}</Link> </li> </ul> - </> + </div> ); } diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityProfiles.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityProfiles.tsx index 6f2fbb1e7b9..8153c194e7e 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityProfiles.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityProfiles.tsx @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Badge, Link } from 'design-system'; +import { Badge, Link, SubHeading } from 'design-system'; import React, { useContext, useEffect } from 'react'; import { searchRules } from '../../../../api/rules'; import { LanguagesContext } from '../../../../app/components/languages/LanguagesContext'; @@ -28,11 +28,10 @@ import { Languages } from '../../../../types/languages'; import { ComponentQualityProfile, Dict } from '../../../../types/types'; interface Props { - headerClassName?: string; profiles: ComponentQualityProfile[]; } -export function MetaQualityProfiles({ headerClassName, profiles }: Props) { +export function MetaQualityProfiles({ profiles }: Readonly<Props>) { const [deprecatedByKey, setDeprecatedByKey] = React.useState<Dict<number>>({}); const languages = useContext(LanguagesContext); @@ -61,11 +60,10 @@ export function MetaQualityProfiles({ headerClassName, profiles }: Props) { }, [profiles]); return ( - <> - <h3 className={headerClassName} id="quality-profiles-list"> - {translate('overview.quality_profiles')} - </h3> - <ul className="project-info-list" aria-labelledby="quality-profiles-list"> + <div> + <SubHeading id="quality-profiles-list">{translate('overview.quality_profiles')}</SubHeading> + + <ul className="sw-flex sw-flex-col sw-gap-2" aria-labelledby="quality-profiles-list"> {profiles.map((profile) => ( <ProfileItem key={profile.key} @@ -75,7 +73,7 @@ export function MetaQualityProfiles({ headerClassName, profiles }: Props) { /> ))} </ul> - </> + </div> ); } diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaSize.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaSize.tsx index d3e361ba54d..7593c2a9b32 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaSize.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaSize.tsx @@ -17,13 +17,13 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { DrilldownLink, SizeIndicator } from 'design-system'; +import { DrilldownLink, Note, SizeIndicator, SubHeading } from 'design-system'; import * as React from 'react'; import { translate, translateWithParameters } from '../../../../helpers/l10n'; import { formatMeasure, localizeMetric } from '../../../../helpers/measures'; import { getComponentDrilldownUrl } from '../../../../helpers/urls'; import { ComponentQualifier } from '../../../../types/component'; -import { MetricKey } from '../../../../types/metrics'; +import { MetricKey, MetricType } from '../../../../types/metrics'; import { Component, Measure } from '../../../../types/types'; interface MetaSizeProps { @@ -45,25 +45,25 @@ export default function MetaSize({ component, measures }: MetaSizeProps) { return ( <> - <div className="sw-flex sw-items-center"> - <h3>{localizeMetric(MetricKey.ncloc)}</h3> - <span className="sw-ml-1 small">({translate('project.info.main_branch')})</span> + <div className="sw-flex sw-items-baseline"> + <SubHeading>{localizeMetric(MetricKey.ncloc)}</SubHeading> + <span className="sw-ml-1">({translate('project.info.main_branch')})</span> </div> <div className="sw-flex sw-items-center"> {ncloc && ncloc.value ? ( <> - <DrilldownLink className="huge" to={url}> + <DrilldownLink to={url}> <span aria-label={translateWithParameters( 'project.info.see_more_info_on_x_locs', ncloc.value, )} > - {formatMeasure(ncloc.value, 'SHORT_INT')} + {formatMeasure(ncloc.value, MetricType.ShortInteger)} </span> </DrilldownLink> - <span className="spacer-left"> + <span className="sw-ml-2"> <SizeIndicator value={Number(ncloc.value)} size="xs" /> </span> </> @@ -72,17 +72,15 @@ export default function MetaSize({ component, measures }: MetaSizeProps) { )} {isApp && ( - <span className="huge-spacer-left display-inline-flex-center"> + <span className="sw-inline-flex sw-items-center sw-ml-10"> {projects ? ( <DrilldownLink to={url}> - <span className="big">{formatMeasure(projects.value, 'SHORT_INT')}</span> + <span>{formatMeasure(projects.value, MetricType.ShortInteger)}</span> </DrilldownLink> ) : ( - <span className="big">0</span> + <span>0</span> )} - <span className="little-spacer-left text-muted"> - {translate('metric.projects.name')} - </span> + <Note className="sw-ml-1">{translate('metric.projects.name')}</Note> </span> )} </div> diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaTags.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaTags.tsx index 3f483f7b257..7d43bf16fd1 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaTags.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaTags.tsx @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { MultiSelector, Tags } from 'design-system'; +import { MultiSelector, SubHeading, Tags } from 'design-system'; import { difference, without } from 'lodash'; import React, { useState } from 'react'; import { searchProjectTags, setApplicationTags, setProjectTags } from '../../../../api/components'; @@ -67,7 +67,7 @@ export default function MetaTags(props: Props) { return ( <> - <h3>{translate('tags')}</h3> + <SubHeading>{translate('tags')}</SubHeading> <Tags allowUpdate={canUpdateTags()} ariaTagsListLabel={translate('tags')} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaVisibility.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaVisibility.tsx index 28951a9cbc3..8237ef6ce20 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaVisibility.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaVisibility.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { SubHeading } from 'design-system'; import * as React from 'react'; import PrivacyBadgeContainer from '../../../../components/common/PrivacyBadgeContainer'; import { translate } from '../../../../helpers/l10n'; @@ -30,7 +31,7 @@ interface Props { export default function MetaVisibility({ qualifier, visibility }: Props) { return ( <> - <h3>{translate('visibility')}</h3> + <SubHeading>{translate('visibility')}</SubHeading> <PrivacyBadgeContainer qualifier={qualifier} visibility={visibility} /> </> ); diff --git a/server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx b/server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx index e445738fe8d..90690bf1347 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx @@ -94,7 +94,7 @@ export default function ProjectBadges(props: ProjectBadgesProps) { return ( <div> <SubTitle>{translate('overview.badges.get_badge')}</SubTitle> - <p className="big-spacer-bottom">{translate('overview.badges.description', qualifier)}</p> + <p className="sw-mb-4">{translate('overview.badges.description', qualifier)}</p> <Spinner loading={isLoading || isEmpty(token)}> <div className="sw-flex sw-space-x-4 sw-mb-4"> diff --git a/server/sonar-web/src/main/js/apps/projectInformation/projectRegulatoryReport/RegulatoryReport.tsx b/server/sonar-web/src/main/js/apps/projectInformation/projectRegulatoryReport/RegulatoryReport.tsx index 52140babaff..285e4231f5d 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/projectRegulatoryReport/RegulatoryReport.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/projectRegulatoryReport/RegulatoryReport.tsx @@ -31,14 +31,14 @@ import { useState } from 'react'; import { FormattedMessage } from 'react-intl'; import { getBranches } from '../../../api/branches'; import { getRegulatoryReportUrl } from '../../../api/regulatory-report'; -import DocLink from '../../../components/common/DocLink'; -import { LabelValueSelectOption } from '../../../components/controls/Select'; +import DocumentationLink from '../../../components/common/DocumentationLink'; import { getBranchLikeDisplayName, getBranchLikeKey, isMainBranch, } from '../../../helpers/branch-like'; import { translate } from '../../../helpers/l10n'; +import { LabelValueSelectOption } from '../../../helpers/search'; import { BranchLike } from '../../../types/branch-like'; import { Component } from '../../../types/types'; @@ -143,9 +143,9 @@ export default function RegulatoryReport({ component, branchLike }: Props) { defaultMessage={translate('regulatory_page.available_branches_info.more_info')} values={{ doc_link: ( - <DocLink to="/analyzing-source-code/branches/branch-analysis/#inactive-branches"> + <DocumentationLink to="/analyzing-source-code/branches/branch-analysis/#inactive-branches"> {translate('regulatory_page.available_branches_info.more_info.doc_link')} - </DocLink> + </DocumentationLink> ), }} /> diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchNewCodeDefinitionSettingModal.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchNewCodeDefinitionSettingModal.tsx index 4207fbf5d8d..9050575e121 100644 --- a/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchNewCodeDefinitionSettingModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchNewCodeDefinitionSettingModal.tsx @@ -216,8 +216,8 @@ export default class BranchNewCodeDefinitionSettingModal extends React.PureCompo return ( <Modal - isLarge headerTitle={header} + isLarge onClose={this.requestClose} body={formBody} primaryButton={ diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateAppRenderer.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateAppRenderer.tsx index 06f1922b0ec..fba51f7f6db 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateAppRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityGate/ProjectQualityGateAppRenderer.tsx @@ -38,10 +38,10 @@ import { OptionProps, components } from 'react-select'; import A11ySkipTarget from '../../components/a11y/A11ySkipTarget'; import DisableableSelectOption from '../../components/common/DisableableSelectOption'; import HelpTooltip from '../../components/controls/HelpTooltip'; -import { LabelValueSelectOption } from '../../components/controls/Select'; import Suggestions from '../../components/embed-docs-modal/Suggestions'; import { translate } from '../../helpers/l10n'; import { isDiffMetric } from '../../helpers/measures'; +import { LabelValueSelectOption } from '../../helpers/search'; import { getQualityGateUrl } from '../../helpers/urls'; import { QualityGate } from '../../types/types'; import BuiltInQualityGateBadge from '../quality-gates/components/BuiltInQualityGateBadge'; @@ -230,7 +230,7 @@ export default function ProjectQualityGateAppRenderer(props: ProjectQualityGateA </FlagMessage> )} {needsReanalysis && ( - <FlagMessage className="big-spacer-top abs-width-600" variant="warning"> + <FlagMessage className="sw-mt-4 sw-w-abs-600" variant="warning"> {translate('project_quality_gate.requires_new_analysis')} </FlagMessage> )} diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/AddLanguageModal.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/AddLanguageModal.tsx index 1ad3e8827fe..8f21bfd7f11 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/AddLanguageModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/AddLanguageModal.tsx @@ -22,8 +22,8 @@ import { difference } from 'lodash'; import * as React from 'react'; import { Profile } from '../../../api/quality-profiles'; import withLanguagesContext from '../../../app/components/languages/withLanguagesContext'; -import { LabelValueSelectOption } from '../../../components/controls/Select'; import { translate } from '../../../helpers/l10n'; +import { LabelValueSelectOption } from '../../../helpers/search'; import { Languages } from '../../../types/languages'; import { Dict } from '../../../types/types'; import LanguageProfileSelectOption, { ProfileOption } from './LanguageProfileSelectOption'; diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx index 5cc1da17389..a73449bc232 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/components/LanguageProfileSelectOption.tsx @@ -21,8 +21,8 @@ import { Link } from 'design-system'; import * as React from 'react'; import { components, OptionProps } from 'react-select'; import DisableableSelectOption from '../../../components/common/DisableableSelectOption'; -import { LabelValueSelectOption } from '../../../components/controls/Select'; import { translate } from '../../../helpers/l10n'; +import { LabelValueSelectOption } from '../../../helpers/search'; import { getQualityProfileUrl } from '../../../helpers/urls'; export interface ProfileOption extends LabelValueSelectOption { diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx index 35ba75d85cf..918592fceeb 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx @@ -71,7 +71,6 @@ export default class ProjectsList extends React.PureComponent<Props> { loadMore={this.props.loadMore} loading={loading} ready={!loading} - useMIUIButtons total={total ?? 0} /> </div> diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx index cf212c946d0..930793f377b 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx @@ -81,9 +81,9 @@ function renderFirstLine( /> )} - <h1 className="it__project-card-name" title={name}> + <span className="it__project-card-name" title={name}> <StandoutLink to={getProjectUrl(key)}>{name}</StandoutLink> - </h1> + </span> {qualifier === ComponentQualifier.Application && ( <Tooltip diff --git a/server/sonar-web/src/main/js/apps/projects/styles.css b/server/sonar-web/src/main/js/apps/projects/styles.css index 2fb03a022b1..b5dbdb5aded 100644 --- a/server/sonar-web/src/main/js/apps/projects/styles.css +++ b/server/sonar-web/src/main/js/apps/projects/styles.css @@ -27,10 +27,6 @@ padding-left: 24px; } -.projects-topbar-item .spinner { - top: -1px; -} - .projects-topbar-item.is-last { margin-left: auto; padding-left: 32px; diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectManagementApp.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectManagementApp.tsx index e75ef5ff0a2..94e0c495c6b 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectManagementApp.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectManagementApp.tsx @@ -248,7 +248,6 @@ class ProjectManagementApp extends React.PureComponent<Props, State> { loadMore={this.loadMore} ready={this.state.ready} total={this.state.total} - useMIUIButtons /> </PageContentFontWrapper> </LargeCenteredLayout> diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx index 7648facfb78..e6cf3d19f8f 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx @@ -34,8 +34,8 @@ import { OptionProps, SingleValueProps, components } from 'react-select'; import { Project } from '../../api/project-management'; import withAppStateContext from '../../app/components/app-state/withAppStateContext'; import HelpTooltip from '../../components/controls/HelpTooltip'; -import { LabelValueSelectOption } from '../../components/controls/Select'; import { translate } from '../../helpers/l10n'; +import { LabelValueSelectOption } from '../../helpers/search'; import { AppState } from '../../types/appstate'; import { Visibility } from '../../types/component'; import BulkApplyTemplateModal from './BulkApplyTemplateModal'; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/CaycBadgeTooltip.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/CaycBadgeTooltip.tsx index 94ce309dc64..f6ad7515ffd 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/CaycBadgeTooltip.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/CaycBadgeTooltip.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import DocLink from '../../../components/common/DocLink'; +import DocumentationLink from '../../../components/common/DocumentationLink'; import { translate } from '../../../helpers/l10n'; export default function CaycBadgeTooltip() { @@ -27,9 +27,9 @@ export default function CaycBadgeTooltip() { <p className="spacer-bottom padded-bottom bordered-bottom-cayc"> {translate('quality_gates.cayc.tooltip.message')} </p> - <DocLink to="/user-guide/clean-as-you-code/"> + <DocumentationLink to="/user-guide/clean-as-you-code/"> {translate('quality_gates.cayc.badge.tooltip.learn_more')} - </DocLink> + </DocumentationLink> </div> ); } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx index aeba605928c..dbac85e2521 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx @@ -172,7 +172,7 @@ export default function Conditions({ qualityGate, isFetching }: Readonly<Props>) <HelperHintIcon /> </DocumentationTooltip> )} - <Spinner loading={isFetching} className="it__spinner sw-ml-4 sw-mt-1" /> + <Spinner loading={isFetching} className="sw-ml-4 sw-mt-1" /> </div> <div> {(qualityGate.caycStatus === CaycStatus.NonCompliant || editing) && canEdit && ( diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ThresholdInput.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ThresholdInput.tsx index dea0d0bde68..b832428a495 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/ThresholdInput.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ThresholdInput.tsx @@ -19,7 +19,7 @@ */ import { InputField, InputSelect } from 'design-system'; import * as React from 'react'; -import { LabelValueSelectOption } from '../../../components/controls/Select'; +import { LabelValueSelectOption } from '../../../helpers/search'; import { Metric } from '../../../types/types'; interface Props { diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx index 8bae6f32ed2..08309dd5457 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.tsx @@ -169,7 +169,6 @@ export default class ProfileProjects extends React.PureComponent<Props, State> { </Table> {projects.length > 0 && ( <ListFooter - useMIUIButtons count={projects.length} loadMore={this.loadMore} loading={this.state.loadingMore} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx index f9c5dce61cc..6e21825714a 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx @@ -19,7 +19,7 @@ */ import { Note } from 'design-system'; import * as React from 'react'; -import DocLink from '../../../components/common/DocLink'; +import DocumentationLink from '../../../components/common/DocumentationLink'; import { translate } from '../../../helpers/l10n'; import { getBaseUrl } from '../../../helpers/system'; @@ -60,9 +60,9 @@ export default function EmptyHotspotsPage(props: EmptyHotspotsPageProps) { {translate(`hotspots.${translationRoot}.description`)} </Note> {!(filtered || isStaticListOfHotspots) && ( - <DocLink className="big-spacer-top" to="/user-guide/security-hotspots/"> + <DocumentationLink className="sw-mt-4" to="/user-guide/security-hotspots/"> {translate('hotspots.learn_more')} - </DocLink> + </DocumentationLink> )} </div> ); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotDisabledFilterTooltip.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotDisabledFilterTooltip.tsx index 6470813cbaf..48accb664f1 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotDisabledFilterTooltip.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotDisabledFilterTooltip.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import DocLink from '../../../components/common/DocLink'; +import DocumentationLink from '../../../components/common/DocumentationLink'; import { translate } from '../../../helpers/l10n'; export function HotspotDisabledFilterTooltip() { @@ -30,7 +30,7 @@ export function HotspotDisabledFilterTooltip() { </p> <hr className="sw-mx-0 sw-my-3 sw-p-0 sw-w-full" /> <span className="sw-body-sm-highlight">{translate('indexation.learn_more')}</span> - <DocLink + <DocumentationLink className="sw-ml-1" onMouseDown={(e) => { // This tooltip content is rendered in the context of a <Dropdown>, and <DropdownToggler> @@ -41,7 +41,7 @@ export function HotspotDisabledFilterTooltip() { to="/instance-administration/reindexing/" > {translate('indexation.reindexing')} - </DocLink> + </DocumentationLink> </div> ); } diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx index 4cfbdfe5f17..cbc7cc4ca8a 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx @@ -192,7 +192,6 @@ export default class HotspotList extends React.Component<Props, State> { loadMore={!loadingMore ? this.props.onLoadMore : undefined} loading={loadingMore} total={hotspotsTotal} - useMIUIButtons /> </StyledContainer> ); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx index 4f5e97340cd..45f70529053 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx @@ -32,7 +32,7 @@ import { import * as React from 'react'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import IssueChangelogDiff from '../../../components/issue/components/IssueChangelogDiff'; -import LegacyAvatar from '../../../components/ui/LegacyAvatar'; +import Avatar from '../../../components/ui/Avatar'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { sanitizeUserInput } from '../../../helpers/sanitize'; import { Hotspot, ReviewHistoryType } from '../../../types/security-hotspots'; @@ -63,7 +63,7 @@ export default function HotspotReviewHistory(props: HotspotReviewHistoryProps) { <LightLabel as="div" className="sw-flex sw-gap-2"> {user.name && ( <div className="sw-flex sw-items-center sw-gap-1"> - <LegacyAvatar hash={user.avatar} name={user.name} size={20} /> + <Avatar hash={user.avatar} name={user.name} size="xs" /> <span className="sw-body-sm-highlight"> {user.active ? user.name : translateWithParameters('user.x_deleted', user.name)} </span> diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx index b7efcfd4c82..7ff7023ca18 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx @@ -145,7 +145,7 @@ export default function HotspotSnippetContainerRenderer( )} <SourceFileWrapper className="sw-box-border sw-w-full sw-rounded-1" ref={scrollableRef}> - <Spinner className="big-spacer" loading={loading} /> + <Spinner className="sw-m-4" loading={loading} /> {!loading && sourceLines.length > 0 && ( <SnippetViewer diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotDisabledFilterTooltip-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotDisabledFilterTooltip-test.tsx.snap index 6e09e138b58..d6d044ecf9d 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotDisabledFilterTooltip-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotDisabledFilterTooltip-test.tsx.snap @@ -1,6 +1,49 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should render correctly and stop event propagation 1`] = ` +.emotion-0 { + color: var(--color); + border-bottom: var(--border); + font-weight: 600; + text-decoration-line: none; + --color: rgb(93,108,208); + --active: rgb(75,86,187); + --border: 1px solid rgb(159,169,237); + --borderActive: 1px solid rgb(159,169,237); +} + +.emotion-0:visited { + color: var(--color); +} + +.emotion-0:hover, +.emotion-0:focus, +.emotion-0:active { + color: var(--active); + border-bottom: var(--borderActive); +} + +.emotion-0:hover .emotion-10, +.emotion-0:focus .emotion-10, +.emotion-0:active .emotion-10 { + color: rgb(123,135,217); +} + +.emotion-0>svg { + vertical-align: text-bottom!important; +} + +.e1vbniy50 .emotion-0 { + --color: rgb(189,198,255); + --active: rgb(209,215,254); + --border: 1px solid rgb(159,169,237); + --borderActive: 1px solid rgb(159,169,237); +} + +.emotion-2 { + color: rgb(159,169,237); +} + <div> <div class="sw-body-sm sw-w-[190px]" @@ -19,30 +62,27 @@ exports[`should render correctly and stop event propagation 1`] = ` indexation.learn_more </span> <a - class="sw-ml-1" + class="sw-ml-1 emotion-0 emotion-1" href="https://docs.sonarsource.com/sonarqube/latest/instance-administration/reindexing/" rel="noopener noreferrer" target="_blank" > + indexation.reindexing <svg - class="little-spacer-right" - height="14" - style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 1.41421;" - version="1.1" + aria-hidden="true" + class="sw-ml-1 emotion-2 emotion-10" + fill="currentColor" + focusable="false" + height="16" + role="img" + style="display: inline-block; user-select: none; vertical-align: middle; overflow: visible;" viewBox="0 0 16 16" - width="14" - xml:space="preserve" - xmlns:xlink="http://www.w3.org/1999/xlink" + width="16" > - <title> - opens_in_new_window - </title> <path - d="M12 9.25v2.5A2.25 2.25 0 0 1 9.75 14h-6.5A2.25 2.25 0 0 1 1 11.75v-6.5A2.25 2.25 0 0 1 3.25 3h5.5c.14 0 .25.11.25.25v.5c0 .14-.11.25-.25.25h-5.5C2.562 4 2 4.563 2 5.25v6.5c0 .688.563 1.25 1.25 1.25h6.5c.688 0 1.25-.563 1.25-1.25v-2.5c0-.14.11-.25.25-.25h.5c.14 0 .25.11.25.25zm3-6.75v4c0 .273-.227.5-.5.5a.497.497 0 0 1-.352-.148l-1.375-1.375L7.68 10.57a.27.27 0 0 1-.18.078.27.27 0 0 1-.18-.078l-.89-.89a.27.27 0 0 1-.078-.18.27.27 0 0 1 .078-.18l5.093-5.093-1.375-1.375A.497.497 0 0 1 10 2.5c0-.273.227-.5.5-.5h4c.273 0 .5.227.5.5z" - style="fill: currentColor;" + d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z" /> </svg> - indexation.reindexing </a> </div> </div> diff --git a/server/sonar-web/src/main/js/apps/settings/components/NewCodeDefinition.tsx b/server/sonar-web/src/main/js/apps/settings/components/NewCodeDefinition.tsx index 64e9416c45e..8191401ce61 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/NewCodeDefinition.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/NewCodeDefinition.tsx @@ -21,7 +21,7 @@ import classNames from 'classnames'; import { Spinner } from 'design-system'; import React, { useCallback, useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; -import DocLink from '../../../components/common/DocLink'; +import DocumentationLink from '../../../components/common/DocumentationLink'; import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons'; import NewCodeDefinitionDaysOption from '../../../components/new-code-definition/NewCodeDefinitionDaysOption'; import NewCodeDefinitionPreviousVersionOption from '../../../components/new-code-definition/NewCodeDefinitionPreviousVersionOption'; @@ -102,9 +102,9 @@ export default function NewCodeDefinition() { id="settings.new_code_period.description3" values={{ link: ( - <DocLink to="/project-administration/defining-new-code/"> + <DocumentationLink to="/project-administration/defining-new-code/"> {translate('settings.new_code_period.description3.link')} - </DocLink> + </DocumentationLink> ), }} /> @@ -146,8 +146,12 @@ export default function NewCodeDefinition() { } settingLevel={NewCodeDefinitionLevels.Global} /> - <div className="big-spacer-top"> - <p className={classNames('spacer-bottom', { invisible: !isFormTouched })}> + <div className="sw-mt-4"> + <p + className={classNames('spacer-bottom', { + 'sw-invisible': !isFormTouched, + })} + > {translate('baseline.next_analysis_notice')} </p> <Spinner className="spacer-right" loading={isSaving} /> diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodeDefinition-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodeDefinition-it.tsx index 48cb1b2c460..57910c31867 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodeDefinition-it.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodeDefinition-it.tsx @@ -23,7 +23,7 @@ import { MessageTypes } from '../../../../api/messages'; import MessagesServiceMock from '../../../../api/mocks/MessagesServiceMock'; import NewCodeDefinitionServiceMock from '../../../../api/mocks/NewCodeDefinitionServiceMock'; import { renderComponent } from '../../../../helpers/testReactTestingUtils'; -import { byLabelText, byRole, byText } from '../../../../helpers/testSelector'; +import { byRole, byText } from '../../../../helpers/testSelector'; import { NewCodeDefinitionType } from '../../../../types/new-code-definition'; import NewCodeDefinition from '../NewCodeDefinition'; @@ -50,7 +50,7 @@ const ui = { saveButton: byRole('button', { name: 'save' }), cancelButton: byRole('button', { name: 'cancel' }), ncdAutoUpdateMessage: byText(/new_code_definition.auto_update.ncd_page.message/), - ncdAutoUpdateMessageDismiss: byLabelText('alert.dismiss'), + ncdAutoUpdateMessageDismiss: byRole('button', { name: 'dismiss' }), }; it('renders and behaves as expected', async () => { diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx index 1363b3287a3..0d0f10abceb 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx @@ -18,14 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import classNames from 'classnames'; -import { FlagMessage, Link, SubTitle, ToggleButton } from 'design-system'; +import { FlagMessage, Link, SubTitle, ToggleButton, getTabId, getTabPanelId } from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { useSearchParams } from 'react-router-dom'; import withAvailableFeatures, { WithAvailableFeaturesProps, } from '../../../../app/components/available-features/withAvailableFeatures'; -import { getTabId, getTabPanelId } from '../../../../components/controls/BoxedTabs'; import { translate } from '../../../../helpers/l10n'; import { getBaseUrl } from '../../../../helpers/system'; import { searchParamsToQuery } from '../../../../helpers/urls'; @@ -152,7 +151,7 @@ export function Authentication(props: Props & WithAvailableFeaturesProps) { {tabs.map((tab) => ( <div className={classNames('sw-overflow-y-auto', { - hidden: currentTab !== tab.value, + 'sw-hidden': currentTab !== tab.value, })} key={tab.value} role="tabpanel" diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/AuthenticationFormFieldWrapper.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/AuthenticationFormFieldWrapper.tsx deleted file mode 100644 index 5aa58281303..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/authentication/AuthenticationFormFieldWrapper.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import React, { PropsWithChildren } from 'react'; -import MandatoryFieldMarker from '../../../../components/ui/MandatoryFieldMarker'; - -interface Props { - readonly title: string; - readonly description?: string; - readonly defKey?: string; - readonly mandatory?: boolean; -} - -export default function AuthenticationFormFieldWrapper(props: PropsWithChildren<Props>) { - const { mandatory = false, title, description, defKey, children } = props; - - return ( - <div className="settings-definition"> - <div className="settings-definition-left"> - <label className="h3" htmlFor={defKey}> - {title} - </label> - {mandatory && <MandatoryFieldMarker />} - {description && <div className="markdown small spacer-top">{description}</div>} - </div> - <div className="settings-definition-right big-padded-top display-flex-column">{children}</div> - </div> - ); -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/AutoProvisionningConsent.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/AutoProvisionningConsent.tsx index 14f5b85ba27..f41125fd67f 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/authentication/AutoProvisionningConsent.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/AutoProvisionningConsent.tsx @@ -17,11 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { ButtonSecondary, Modal } from 'design-system'; +import { noop } from 'lodash'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import DocLink from '../../../../components/common/DocLink'; -import Modal from '../../../../components/controls/Modal'; -import { Button } from '../../../../components/controls/buttons'; +import DocumentationLink from '../../../../components/common/DocumentationLink'; import { translate } from '../../../../helpers/l10n'; import { useToggleGithubProvisioningMutation } from '../../../../queries/identity-provider/github'; import { useGetValueQuery, useResetSettingsMutation } from '../../../../queries/settings'; @@ -55,11 +55,9 @@ export default function AutoProvisioningConsent() { } return ( - <Modal contentLabel={header} shouldCloseOnOverlayClick={false} size="medium"> - <header className="modal-head"> - <h2>{header}</h2> - </header> - <div className="modal-body"> + <Modal onClose={noop} closeOnOverlayClick={false} isLarge> + <Modal.Header title={header} /> + <Modal.Body> <FormattedMessage tagName="p" id="settings.authentication.github.confirm_auto_provisioning.description1" @@ -69,9 +67,9 @@ export default function AutoProvisioningConsent() { tagName="p" values={{ documentation: ( - <DocLink to="/instance-administration/authentication/github/"> + <DocumentationLink to="/instance-administration/authentication/github/"> <FormattedMessage id="documentation" /> - </DocLink> + </DocumentationLink> ), }} /> @@ -79,15 +77,19 @@ export default function AutoProvisioningConsent() { tagName="p" id="settings.authentication.github.confirm_auto_provisioning.question" /> - </div> - <footer className="modal-foot"> - <Button onClick={continueWithAuto}> - {translate('settings.authentication.github.confirm_auto_provisioning.continue')} - </Button> - <Button onClick={switchToJIT}> - {translate('settings.authentication.github.confirm_auto_provisioning.switch_jit')} - </Button> - </footer> + </Modal.Body> + <Modal.Footer + primaryButton={ + <ButtonSecondary onClick={continueWithAuto}> + {translate('settings.authentication.github.confirm_auto_provisioning.continue')} + </ButtonSecondary> + } + secondaryButton={ + <ButtonSecondary onClick={switchToJIT}> + {translate('settings.authentication.github.confirm_auto_provisioning.switch_jit')} + </ButtonSecondary> + } + /> </Modal> ); } diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForBoolean.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForBoolean.tsx index 21e26e93f85..3dcc86492cd 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForBoolean.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForBoolean.tsx @@ -19,7 +19,6 @@ */ import { Note, Switch } from 'design-system'; import * as React from 'react'; -import { getToggleValue } from '../../../../components/controls/Toggle'; import { translate } from '../../../../helpers/l10n'; import { DefaultSpecializedInputProps, getPropertyName } from '../../utils'; @@ -47,3 +46,7 @@ export default function InputForBoolean({ onChange, name, value, setting }: Prop </div> ); } + +function getToggleValue(value: string | boolean) { + return typeof value === 'string' ? value === 'true' : value; +} diff --git a/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx b/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx index ba081395b03..b120142bb20 100644 --- a/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx +++ b/server/sonar-web/src/main/js/apps/settings/encryption/GenerateSecretKeyForm.tsx @@ -22,6 +22,7 @@ import { ClipboardIconButton, CodeSnippet, ListItem, + Spinner, SubHeading, UnorderedList, } from 'design-system'; @@ -29,7 +30,6 @@ import * as React from 'react'; import { useCallback, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import DocumentationLink from '../../../components/common/DocumentationLink'; -import Spinner from '../../../components/ui/Spinner'; import { translate } from '../../../helpers/l10n'; interface Props { diff --git a/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TutorialsApp-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TutorialsApp-test.tsx index bcfd66e9ba8..d8730fc71ec 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TutorialsApp-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/components/__tests__/TutorialsApp-test.tsx @@ -22,7 +22,7 @@ import UserTokensMock from '../../../../api/mocks/UserTokensMock'; import handleRequiredAuthentication from '../../../../helpers/handleRequiredAuthentication'; import { mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks'; import { renderAppWithComponentContext } from '../../../../helpers/testReactTestingUtils'; -import { byLabelText, byRole } from '../../../../helpers/testSelector'; +import { byRole, byText } from '../../../../helpers/testSelector'; import { Permissions } from '../../../../types/permissions'; import routes from '../../routes'; @@ -44,7 +44,7 @@ afterEach(() => { beforeEach(jest.clearAllMocks); const ui = { - loading: byLabelText('loading'), + loading: byText('loading'), localScanButton: byRole('heading', { name: 'onboarding.tutorial.choose_method' }), }; diff --git a/server/sonar-web/src/main/js/apps/users/UsersApp.tsx b/server/sonar-web/src/main/js/apps/users/UsersApp.tsx index 27ca918c6ca..13c7b35368e 100644 --- a/server/sonar-web/src/main/js/apps/users/UsersApp.tsx +++ b/server/sonar-web/src/main/js/apps/users/UsersApp.tsx @@ -35,10 +35,10 @@ import GitLabSynchronisationWarning from '../../app/components/GitLabSynchronisa import HelpTooltip from '../../components/controls/HelpTooltip'; import ListFooter from '../../components/controls/ListFooter'; import { ManagedFilter } from '../../components/controls/ManagedFilter'; -import { LabelValueSelectOption } from '../../components/controls/Select'; import Suggestions from '../../components/embed-docs-modal/Suggestions'; import { now, toISO8601WithOffsetString } from '../../helpers/dates'; import { translate } from '../../helpers/l10n'; +import { LabelValueSelectOption } from '../../helpers/search'; import { useIdentityProviderQuery } from '../../queries/identity-provider/common'; import { useUsersQueries } from '../../queries/users'; import { IdentityProvider, Provider } from '../../types/types'; @@ -163,7 +163,6 @@ export default function UsersApp() { loadMore={fetchNextPage} ready={!isLoading} total={data?.pages[0].page.total} - useMIUIButtons /> </PageContentFontWrapper> </LargeCenteredLayout> diff --git a/server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx b/server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx index 5e08254328d..bb7ffc68318 100644 --- a/server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx @@ -105,7 +105,7 @@ export default function PasswordForm(props: Props) { type="password" value={oldPassword} /> - <input className="hidden" aria-hidden name="old-password-fake" type="password" /> + <input className="sw-hidden" aria-hidden name="old-password-fake" type="password" /> </FormField> )} <FormField htmlFor="user-password" label={translate('my_profile.password.new')} required> @@ -119,7 +119,7 @@ export default function PasswordForm(props: Props) { value={newPassword} size="full" /> - <input className="hidden" aria-hidden name="password-fake" type="password" /> + <input className="sw-hidden" aria-hidden name="password-fake" type="password" /> </FormField> <FormField htmlFor="confirm-user-password" @@ -136,7 +136,7 @@ export default function PasswordForm(props: Props) { value={confirmPassword} size="full" /> - <input className="hidden" aria-hidden name="confirm-password-fake" type="password" /> + <input className="sw-hidden" aria-hidden name="confirm-password-fake" type="password" /> </FormField> </form> } diff --git a/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx b/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx index 42b2cbaa6ae..ff745df2e81 100644 --- a/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx @@ -33,8 +33,8 @@ import { isEmpty } from 'lodash'; import * as React from 'react'; import { getScannableProjects } from '../../../api/components'; import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext'; -import { LabelValueSelectOption } from '../../../components/controls/Select'; import { translate } from '../../../helpers/l10n'; +import { LabelValueSelectOption } from '../../../helpers/search'; import { EXPIRATION_OPTIONS, computeTokenExpirationDate, diff --git a/server/sonar-web/src/main/js/apps/users/constants.ts b/server/sonar-web/src/main/js/apps/users/constants.ts index df9a71f5bab..374efbf68c6 100644 --- a/server/sonar-web/src/main/js/apps/users/constants.ts +++ b/server/sonar-web/src/main/js/apps/users/constants.ts @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { LabelValueSelectOption } from '../../components/controls/Select'; import { translate } from '../../helpers/l10n'; +import { LabelValueSelectOption } from '../../helpers/search'; import { UserActivity } from './types'; // Nb of days without connection to SQ after which a user is considered inactive: diff --git a/server/sonar-web/src/main/js/apps/web-api/__tests__/WebApi-it.tsx b/server/sonar-web/src/main/js/apps/web-api/__tests__/WebApi-it.tsx index db5c836e043..18805f97356 100644 --- a/server/sonar-web/src/main/js/apps/web-api/__tests__/WebApi-it.tsx +++ b/server/sonar-web/src/main/js/apps/web-api/__tests__/WebApi-it.tsx @@ -88,8 +88,8 @@ function renderWebApi(navigateTo?: string) { } const ui = { - domainMenuItems: byRole('menu').byRole('listitem'), - domainMenuItemLink: (name: string) => byRole('menu').byRole('link', { name }), + domainMenuItems: byRole('navigation').byRole('link'), + domainMenuItemLink: (name: string) => byRole('navigation').byRole('link', { name }), domainHeader: (name: string) => byRole('heading', { level: 2, name }), sidebarHeader: byRole('heading', { name: 'api_documentation.page' }), searchInput: byLabelText('api_documentation.search'), diff --git a/server/sonar-web/src/main/js/apps/web-api/components/Action.tsx b/server/sonar-web/src/main/js/apps/web-api/components/Action.tsx index 6ddbae01168..017c0096f98 100644 --- a/server/sonar-web/src/main/js/apps/web-api/components/Action.tsx +++ b/server/sonar-web/src/main/js/apps/web-api/components/Action.tsx @@ -17,10 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import classNames from 'classnames'; +import { Badge, Card, LinkBox, LinkIcon, SubHeading, Tabs } from 'design-system'; import * as React from 'react'; -import Link from '../../../components/common/Link'; -import LinkIcon from '../../../components/icons/LinkIcon'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { queryToSearch } from '../../../helpers/urls'; import { WebApi } from '../../../types/types'; @@ -38,168 +36,88 @@ interface Props { showInternal: boolean; } -interface State { - showChangelog: boolean; - showParams: boolean; - showResponse: boolean; +enum TabOption { + PARAMS = 'parameters', + RESPONSE = 'response_example', + CHANGELOG = 'changelog', } -export default class Action extends React.PureComponent<Props, State> { - state: State = { - showChangelog: false, - showParams: false, - showResponse: false, - }; - - handleShowParamsClick = (e: React.SyntheticEvent<HTMLElement>) => { - e.preventDefault(); - this.setState((state) => ({ - showChangelog: false, - showResponse: false, - showParams: !state.showParams, - })); - }; - - handleShowResponseClick = (e: React.SyntheticEvent<HTMLElement>) => { - e.preventDefault(); - this.setState((state) => ({ - showChangelog: false, - showParams: false, - showResponse: !state.showResponse, - })); - }; - - handleChangelogClick = (e: React.SyntheticEvent<HTMLElement>) => { - e.preventDefault(); - this.setState((state) => ({ - showChangelog: !state.showChangelog, - showParams: false, - showResponse: false, - })); - }; - - renderTabs() { - const { action } = this.props; - const { showChangelog, showParams, showResponse } = this.state; - - if (action.params || action.hasResponseExample || action.changelog.length > 0) { - return ( - <ul className="web-api-action-actions tabs"> - {action.params && ( - <li> - <a - className={classNames({ selected: showParams })} - href="#" - onClick={this.handleShowParamsClick} - > - {translate('api_documentation.parameters')} - </a> - </li> - )} - - {action.hasResponseExample && ( - <li> - <a - className={classNames({ selected: showResponse })} - href="#" - onClick={this.handleShowResponseClick} - > - {translate('api_documentation.response_example')} - </a> - </li> - )} - - {action.changelog.length > 0 && ( - <li> - <a - className={classNames({ selected: showChangelog })} - href="#" - onClick={this.handleChangelogClick} - > - {translate('api_documentation.changelog')} - </a> - </li> - )} - </ul> - ); - } +export default function Action(props: Props) { + const { action, domain, showDeprecated, showInternal } = props; + const verb = action.post ? 'POST' : 'GET'; + const actionKey = getActionKey(domain.path, action.key); - return null; - } - - render() { - const { action, domain } = this.props; - const { showChangelog, showParams, showResponse } = this.state; - const verb = action.post ? 'POST' : 'GET'; - const actionKey = getActionKey(domain.path, action.key); - - return ( - <div className="boxed-group" id={actionKey}> - <header className="web-api-action-header boxed-group-header"> - <Link - className="spacer-right link-no-underline" - to={{ - pathname: '/web_api/' + actionKey, - search: queryToSearch( - serializeQuery({ - deprecated: Boolean(action.deprecatedSince), - internal: Boolean(action.internal), - }), - ), - }} - > - <LinkIcon /> - </Link> - - <h3 className="web-api-action-title"> - {verb} - - {actionKey} - </h3> - - {action.internal && ( - <span className="spacer-left"> - <InternalBadge /> - </span> - )} - - {action.since && ( - <span className="spacer-left badge"> - {translateWithParameters('since_x', action.since)} - </span> - )} - - {action.deprecatedSince && ( - <span className="spacer-left"> - <DeprecatedBadge since={action.deprecatedSince} /> - </span> - )} - </header> - - <div className="boxed-group-inner"> - <div - className="web-api-action-description markdown" - // Safe: comes from the backend - dangerouslySetInnerHTML={{ __html: action.description }} - /> + const [tab, setTab] = React.useState<TabOption | undefined>(undefined); - {this.renderTabs()} + const tabOptions = React.useMemo(() => { + const opts = []; - {showParams && action.params && ( - <Params - params={action.params} - showDeprecated={this.props.showDeprecated} - showInternal={this.props.showInternal} - /> - )} + if (action.params) { + opts.push(TabOption.PARAMS); + } + if (action.hasResponseExample) { + opts.push(TabOption.RESPONSE); + } + if (action.changelog.length > 0) { + opts.push(TabOption.CHANGELOG); + } + + return opts.map((option) => ({ label: translate('api_documentation', option), value: option })); + }, [action]); + + return ( + <Card id={actionKey}> + <header className="sw-flex sw-items-baseline sw-gap-2"> + <LinkBox + to={{ + pathname: '/web_api/' + actionKey, + search: queryToSearch( + serializeQuery({ + deprecated: Boolean(action.deprecatedSince), + internal: Boolean(action.internal), + }), + ), + }} + > + <LinkIcon /> + </LinkBox> + + <SubHeading className="sw-m-0"> + {verb} {actionKey} + </SubHeading> + + {action.internal && <InternalBadge />} + + {action.since && ( + <Badge variant="new">{translateWithParameters('since_x', action.since)}</Badge> + )} + + {action.deprecatedSince && <DeprecatedBadge since={action.deprecatedSince} />} + </header> + + <div + className="sw-mt-4 markdown" + // Safe: comes from the backend + dangerouslySetInnerHTML={{ __html: action.description }} + /> + + <div className="sw-mt-4"> + <Tabs options={tabOptions} onChange={(opt) => setTab(opt)} value={tab} /> + + {tab === TabOption.PARAMS && action.params && ( + <Params + params={action.params} + showDeprecated={showDeprecated} + showInternal={showInternal} + /> + )} - {showResponse && action.hasResponseExample && ( - <ResponseExample action={action} domain={domain} /> - )} + {tab === TabOption.RESPONSE && action.hasResponseExample && ( + <ResponseExample action={action} domain={domain} /> + )} - {showChangelog && <ActionChangelog changelog={action.changelog} />} - </div> + {tab === TabOption.CHANGELOG && <ActionChangelog changelog={action.changelog} />} </div> - ); - } + </Card> + ); } diff --git a/server/sonar-web/src/main/js/apps/web-api/components/ActionChangelog.tsx b/server/sonar-web/src/main/js/apps/web-api/components/ActionChangelog.tsx index 55d38486456..c49cfe00f72 100644 --- a/server/sonar-web/src/main/js/apps/web-api/components/ActionChangelog.tsx +++ b/server/sonar-web/src/main/js/apps/web-api/components/ActionChangelog.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Badge, ListItem, UnorderedList } from 'design-system'; import * as React from 'react'; import { WebApi } from '../../../types/types'; @@ -26,13 +27,15 @@ interface Props { export default function ActionChangelog({ changelog }: Props) { return ( - <ul className="big-spacer-top"> + <UnorderedList> {changelog.map((item, index) => ( - <li className="spacer-top" key={index}> - <span className="spacer-right badge">{item.version}</span> + <ListItem key={index}> + <Badge variant="default" className="sw-mr-2"> + {item.version} + </Badge> {item.description} - </li> + </ListItem> ))} - </ul> + </UnorderedList> ); } diff --git a/server/sonar-web/src/main/js/apps/web-api/components/DeprecatedBadge.tsx b/server/sonar-web/src/main/js/apps/web-api/components/DeprecatedBadge.tsx index 11882f45566..6cf82658848 100644 --- a/server/sonar-web/src/main/js/apps/web-api/components/DeprecatedBadge.tsx +++ b/server/sonar-web/src/main/js/apps/web-api/components/DeprecatedBadge.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Badge } from 'design-system'; import * as React from 'react'; import Tooltip from '../../../components/controls/Tooltip'; import { translate, translateWithParameters } from '../../../helpers/l10n'; @@ -32,7 +33,9 @@ export default function DeprecatedBadge({ since }: { since?: string }) { : translate('api_documentation.deprecated'); return ( <Tooltip overlay={overlay}> - <span className="badge badge-warning">{label}</span> + <span> + <Badge variant="default">{label}</Badge> + </span> </Tooltip> ); } diff --git a/server/sonar-web/src/main/js/apps/web-api/components/Domain.tsx b/server/sonar-web/src/main/js/apps/web-api/components/Domain.tsx index aa072b597ce..711dc6c8c4a 100644 --- a/server/sonar-web/src/main/js/apps/web-api/components/Domain.tsx +++ b/server/sonar-web/src/main/js/apps/web-api/components/Domain.tsx @@ -17,9 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { SubTitle } from 'design-system'; +import { isEmpty } from 'lodash'; import * as React from 'react'; import { WebApi } from '../../../types/types'; -import { actionsFilter, getActionKey, Query } from '../utils'; +import { Query, actionsFilter, getActionKey } from '../utils'; import Action from './Action'; import DeprecatedBadge from './DeprecatedBadge'; import InternalBadge from './InternalBadge'; @@ -33,32 +35,24 @@ export default function Domain({ domain, query }: Props) { const filteredActions = domain.actions.filter((action) => actionsFilter(query, domain, action)); return ( - <div className="web-api-domain"> - <header className="web-api-domain-header"> - <h2 className="web-api-domain-title">{domain.path}</h2> + <div> + <header className="sw-flex sw-items-baseline sw-gap-3"> + <SubTitle className="sw-m-0">{domain.path}</SubTitle> - {domain.deprecatedSince && ( - <span className="spacer-left"> - <DeprecatedBadge since={domain.deprecatedSince} /> - </span> - )} + {!isEmpty(domain.deprecatedSince) && <DeprecatedBadge since={domain.deprecatedSince} />} - {domain.internal && ( - <span className="spacer-left"> - <InternalBadge /> - </span> - )} + {domain.internal && <InternalBadge />} </header> - {domain.description && ( + {!isEmpty(domain.description) && ( <div - className="web-api-domain-description markdown" + className="sw-mt-3 markdown" // Safe: comes from the backend dangerouslySetInnerHTML={{ __html: domain.description }} /> )} - <div className="web-api-domain-actions"> + <div className="sw-mt-4 sw-flex sw-flex-col sw-gap-4"> {filteredActions.map((action) => ( <Action action={action} diff --git a/server/sonar-web/src/main/js/apps/web-api/components/InternalBadge.tsx b/server/sonar-web/src/main/js/apps/web-api/components/InternalBadge.tsx index 271afad9c5b..f659020d052 100644 --- a/server/sonar-web/src/main/js/apps/web-api/components/InternalBadge.tsx +++ b/server/sonar-web/src/main/js/apps/web-api/components/InternalBadge.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Badge } from 'design-system'; import * as React from 'react'; import Tooltip from '../../../components/controls/Tooltip'; import { translate } from '../../../helpers/l10n'; @@ -24,7 +25,9 @@ import { translate } from '../../../helpers/l10n'; export default function InternalBadge() { return ( <Tooltip overlay={translate('api_documentation.internal_tooltip')}> - <span className="badge badge-error">{translate('internal')}</span> + <span> + <Badge variant="deleted">{translate('internal')}</Badge> + </span> </Tooltip> ); } diff --git a/server/sonar-web/src/main/js/apps/web-api/components/Menu.tsx b/server/sonar-web/src/main/js/apps/web-api/components/Menu.tsx index f6fa855e78d..5943362405d 100644 --- a/server/sonar-web/src/main/js/apps/web-api/components/Menu.tsx +++ b/server/sonar-web/src/main/js/apps/web-api/components/Menu.tsx @@ -17,12 +17,12 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import classNames from 'classnames'; +import { SubnavigationGroup, SubnavigationItem } from 'design-system'; import * as React from 'react'; -import Link from '../../../components/common/Link'; +import { useNavigate } from 'react-router-dom'; import { queryToSearch } from '../../../helpers/urls'; import { WebApi } from '../../../types/types'; -import { actionsFilter, isDomainPathActive, Query, serializeQuery } from '../utils'; +import { Query, actionsFilter, isDomainPathActive, serializeQuery } from '../utils'; import DeprecatedBadge from './DeprecatedBadge'; import InternalBadge from './InternalBadge'; @@ -34,6 +34,19 @@ interface Props { export default function Menu(props: Props) { const { domains, query, splat } = props; + + const navigateTo = useNavigate(); + + const showDomain = React.useCallback( + (domainPath: string) => { + navigateTo({ + pathname: '/web_api/' + domainPath, + search: queryToSearch(serializeQuery(query)), + }); + }, + [query, navigateTo], + ); + const filteredDomains = (domains || []) .map((domain) => { const filteredActions = domain.actions.filter((action) => @@ -45,29 +58,23 @@ export default function Menu(props: Props) { const renderDomain = (domain: WebApi.Domain) => { const internal = !domain.actions.find((action) => !action.internal); + return ( - <li - className={classNames('list-group-item sw-p-0', { - active: isDomainPathActive(domain.path, splat), - })} + <SubnavigationItem + active={isDomainPathActive(domain.path, splat)} + onClick={() => showDomain(domain.path)} key={domain.path} > - <Link - to={{ pathname: '/web_api/' + domain.path, search: queryToSearch(serializeQuery(query)) }} - > - <h3 className="sw-truncate sw-px-2 sw-py-3"> - {domain.path} - {domain.deprecatedSince && <DeprecatedBadge since={domain.deprecatedSince} />} - {internal && <InternalBadge />} - </h3> - </Link> - </li> + {domain.path} + {domain.deprecatedSince && <DeprecatedBadge since={domain.deprecatedSince} />} + {internal && <InternalBadge />} + </SubnavigationItem> ); }; return ( - <div className="api-documentation-results panel" role="menu"> - <ul className="list-group">{filteredDomains.map(renderDomain)}</ul> - </div> + <SubnavigationGroup className="sw-mt-4 sw-box-border"> + {filteredDomains.map(renderDomain)} + </SubnavigationGroup> ); } diff --git a/server/sonar-web/src/main/js/apps/web-api/components/Params.tsx b/server/sonar-web/src/main/js/apps/web-api/components/Params.tsx index 71af96386ce..865fc766b09 100644 --- a/server/sonar-web/src/main/js/apps/web-api/components/Params.tsx +++ b/server/sonar-web/src/main/js/apps/web-api/components/Params.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { ContentCell, DarkLabel, HtmlFormatter, Note, Table, TableRow } from 'design-system'; import * as React from 'react'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { WebApi } from '../../../types/types'; @@ -29,44 +30,54 @@ interface Props { showInternal: boolean; } +const TABLE_COLUMNS = ['200', 'auto', '200']; + export default class Params extends React.PureComponent<Props> { renderKey(param: WebApi.Param) { return ( - <td className="markdown" style={{ width: 180 }}> - <code>{param.key}</code> + <ContentCell> + <div> + <HtmlFormatter> + <code className="sw-code">{param.key}</code> + </HtmlFormatter> - {param.internal && ( - <div className="little-spacer-top"> - <InternalBadge /> - </div> - )} + {param.internal && ( + <div className="sw-mt-1"> + <InternalBadge /> + </div> + )} - {param.deprecatedSince && ( - <div className="little-spacer-top"> - <DeprecatedBadge since={param.deprecatedSince} /> - </div> - )} + {param.deprecatedSince && ( + <div className="sw-mt-1"> + <DeprecatedBadge since={param.deprecatedSince} /> + </div> + )} - <div className="note little-spacer-top">{param.required ? 'required' : 'optional'}</div> + <Note as="div" className="sw-mt-1"> + {param.required ? 'required' : 'optional'} + </Note> - {param.since && ( - <div className="note little-spacer-top"> - {translateWithParameters('since_x', param.since)} - </div> - )} + {param.since && ( + <Note as="div" className="sw-mt-1"> + {translateWithParameters('since_x', param.since)} + </Note> + )} - {this.props.showDeprecated && param.deprecatedKey && ( - <div className="big-spacer-top spacer-left"> - <div className="note little-spacer-bottom">{translate('replaces')}:</div> - <code>{param.deprecatedKey}</code> - {param.deprecatedKeySince && ( - <div className="little-spacer-top"> - <DeprecatedBadge since={param.deprecatedKeySince} /> - </div> - )} - </div> - )} - </td> + {this.props.showDeprecated && param.deprecatedKey && ( + <div className="sw-ml-2 sw-mt-4"> + <Note as="div" className="sw-mb-1"> + {translate('replaces')}: + </Note> + <code className="sw-code">{param.deprecatedKey}</code> + {param.deprecatedKeySince && ( + <div className="sw-mt-1"> + <DeprecatedBadge since={param.deprecatedKeySince} /> + </div> + )} + </div> + )} + </div> + </ContentCell> ); } @@ -74,9 +85,9 @@ export default class Params extends React.PureComponent<Props> { const value = param[field]; if (value !== undefined) { return ( - <div className="little-spacer-top"> - <h4>{translate('api_documentation', label)}</h4> - <code>{value}</code> + <div className="sw-mt-1"> + <DarkLabel as="div">{translate('api_documentation', label)}</DarkLabel> + <code className="sw-code">{value}</code> </div> ); } else { @@ -90,28 +101,30 @@ export default class Params extends React.PureComponent<Props> { .filter((p) => showDeprecated || !p.deprecatedSince) .filter((p) => showInternal || !p.internal); return ( - <div className="web-api-params"> - <table> - <tbody> - {displayedParameters.map((param) => ( - <tr key={param.key}> - {this.renderKey(param)} + <div className="sw-mt-6"> + <Table columnCount={TABLE_COLUMNS.length} columnWidths={TABLE_COLUMNS}> + {displayedParameters.map((param) => ( + <TableRow key={param.key}> + {this.renderKey(param)} - <td> - <div - className="markdown" - // Safe: comes from the backend - dangerouslySetInnerHTML={{ __html: param.description }} - /> - </td> + <ContentCell> + <div + className="markdown" + // Safe: comes from the backend + dangerouslySetInnerHTML={{ __html: param.description }} + /> + </ContentCell> - <td style={{ width: 250 }}> + <ContentCell> + <div> {param.possibleValues && ( <div> - <h4>{translate('api_documentation.possible_values')}</h4> - <ul className="list-styled"> + <DarkLabel as="div"> + {translate('api_documentation.possible_values')} + </DarkLabel> + <ul> {param.possibleValues.map((value) => ( - <li className="little-spacer-top" key={value}> + <li className="sw-mt-1" key={value}> <code>{value}</code> </li> ))} @@ -126,11 +139,11 @@ export default class Params extends React.PureComponent<Props> { {this.renderConstraint(param, 'maximumValue', 'max_value')} {this.renderConstraint(param, 'minimumLength', 'min_length')} {this.renderConstraint(param, 'maximumLength', 'max_length')} - </td> - </tr> - ))} - </tbody> - </table> + </div> + </ContentCell> + </TableRow> + ))} + </Table> </div> ); } diff --git a/server/sonar-web/src/main/js/apps/web-api/components/ResponseExample.tsx b/server/sonar-web/src/main/js/apps/web-api/components/ResponseExample.tsx index 71d23cd5051..06ef9f4f794 100644 --- a/server/sonar-web/src/main/js/apps/web-api/components/ResponseExample.tsx +++ b/server/sonar-web/src/main/js/apps/web-api/components/ResponseExample.tsx @@ -61,8 +61,10 @@ export default class ResponseExample extends React.PureComponent<Props, State> { const { responseExample } = this.state; return ( - <div className="web-api-response"> - {responseExample && <pre style={{ whiteSpace: 'pre-wrap' }}>{responseExample.example}</pre>} + <div className="sw-mt-6"> + {responseExample && ( + <pre className="sw-code sw-whitespace-pre-wrap">{responseExample.example}</pre> + )} </div> ); } diff --git a/server/sonar-web/src/main/js/apps/web-api/components/Search.tsx b/server/sonar-web/src/main/js/apps/web-api/components/Search.tsx index f1e86864bf2..be7bb705e78 100644 --- a/server/sonar-web/src/main/js/apps/web-api/components/Search.tsx +++ b/server/sonar-web/src/main/js/apps/web-api/components/Search.tsx @@ -17,10 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Checkbox, HelperHintIcon, InputSearch } from 'design-system'; import * as React from 'react'; -import Checkbox from '../../../components/controls/Checkbox'; import HelpTooltip from '../../../components/controls/HelpTooltip'; -import SearchBox from '../../../components/controls/SearchBox'; import { translate } from '../../../helpers/l10n'; import { Query } from '../utils'; @@ -35,35 +34,34 @@ export default function Search(props: Props) { const { query, onToggleInternal, onToggleDeprecated } = props; return ( - <div className="web-api-search"> + <div> <div> - <SearchBox + <InputSearch onChange={props.onSearch} placeholder={translate('api_documentation.search')} value={query.search} /> </div> - <div className="big-spacer-top"> - <Checkbox checked={query.internal} className="text-middle" onCheck={onToggleInternal}> - <span className="little-spacer-left">{translate('api_documentation.show_internal')}</span> + <div className="sw-flex sw-items-center sw-mt-4"> + <Checkbox checked={query.internal} onCheck={onToggleInternal}> + <span className="sw-ml-2">{translate('api_documentation.show_internal')}</span> </Checkbox> - <HelpTooltip - className="spacer-left" - overlay={translate('api_documentation.internal_tooltip')} - /> + <HelpTooltip className="sw-ml-2" overlay={translate('api_documentation.internal_tooltip')}> + <HelperHintIcon /> + </HelpTooltip> </div> - <div className="spacer-top"> - <Checkbox checked={query.deprecated} className="text-middle" onCheck={onToggleDeprecated}> - <span className="little-spacer-left"> - {translate('api_documentation.show_deprecated')} - </span> + <div className="sw-flex sw-items-center sw-mt-2"> + <Checkbox checked={query.deprecated} onCheck={onToggleDeprecated}> + <span className="sw-ml-2">{translate('api_documentation.show_deprecated')}</span> </Checkbox> <HelpTooltip - className="spacer-left" + className="sw-ml-2" overlay={translate('api_documentation.deprecation_tooltip')} - /> + > + <HelperHintIcon /> + </HelpTooltip> </div> </div> ); diff --git a/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx b/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx index a6b589c0d51..d0c92dab643 100644 --- a/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx +++ b/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx @@ -17,14 +17,20 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import styled from '@emotion/styled'; +import { + LAYOUT_FOOTER_HEIGHT, + LAYOUT_GLOBAL_NAV_HEIGHT, + LargeCenteredLayout, + PageContentFontWrapper, + Title, +} from 'design-system'; import { maxBy } from 'lodash'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; import { Params, useParams } from 'react-router-dom'; import { fetchWebApi } from '../../../api/web-api'; import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget'; -import Link from '../../../components/common/Link'; -import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper'; import Suggestions from '../../../components/embed-docs-modal/Suggestions'; import { Location, Router, withRouter } from '../../../components/hoc/withRouter'; import { translate } from '../../../helpers/l10n'; @@ -155,49 +161,47 @@ export class WebApiApp extends React.PureComponent<Props, State> { }; render() { - const splat = this.props.params.splat || ''; + const splat = this.props.params.splat ?? ''; const query = parseQuery(this.props.location.query); const { domains } = this.state; const domain = domains.find((domain) => isDomainPathActive(domain.path, splat)); return ( - <div className="layout-page"> - <Suggestions suggestions="api_documentation" /> - <Helmet defer={false} title={translate('api_documentation.page')} /> - <ScreenPositionHelper className="layout-page-side-outer"> - {({ top }) => ( - <div className="layout-page-side" style={{ top }}> - <div className="layout-page-side-inner"> - <div className="layout-page-filters"> - <A11ySkipTarget anchor="webapi_main" /> - - <div className="web-api-page-header"> - <Link to="/web_api/"> - <h1>{translate('api_documentation.page')}</h1> - </Link> - </div> - - <Search - onSearch={this.handleSearch} - onToggleDeprecated={this.handleToggleDeprecated} - onToggleInternal={this.handleToggleInternal} - query={query} - /> - - <Menu domains={this.state.domains} query={query} splat={splat} /> - </div> + <LargeCenteredLayout> + <PageContentFontWrapper className="sw-body-sm sw-w-full sw-flex"> + <Suggestions suggestions="api_documentation" /> + <Helmet defer={false} title={translate('api_documentation.page')} /> + <div className="sw-w-full sw-flex"> + <NavContainer + aria-label={translate('api_documentation.domain_nav')} + className="sw--mx-2" + > + <A11ySkipTarget anchor="webapi_main" /> + + <Title>{translate('api_documentation.page')}</Title> + + <Search + onSearch={this.handleSearch} + onToggleDeprecated={this.handleToggleDeprecated} + onToggleInternal={this.handleToggleInternal} + query={query} + /> + <div className="sw-w-[300px] sw-mr-2"> + <Menu domains={this.state.domains} query={query} splat={splat} /> </div> - </div> - )} - </ScreenPositionHelper> - - <div className="layout-page-main"> - <div className="layout-page-main-inner"> - {domain && <Domain domain={domain} key={domain.path} query={query} />} + </NavContainer> + <main + style={{ + height: `calc(100vh - ${LAYOUT_FOOTER_HEIGHT + LAYOUT_GLOBAL_NAV_HEIGHT}px`, + }} + className="sw-box-border sw-overflow-y-auto sw-relative sw-flex-1 sw-min-w-0 sw-ml-8 sw-py-8" + > + {domain && <Domain domain={domain} key={domain.path} query={query} />} + </main> </div> - </div> - </div> + </PageContentFontWrapper> + </LargeCenteredLayout> ); } } @@ -224,3 +228,13 @@ function getLatestDeprecatedAction(domain: Pick<WebApi.Domain, 'actions'>) { }); return latestDeprecation || undefined; } + +const NavContainer = styled.nav` + scrollbar-gutter: stable; + overflow-y: auto; + overflow-x: hidden; + box-sizing: border-box; + height: calc(100vh - ${LAYOUT_FOOTER_HEIGHT + LAYOUT_GLOBAL_NAV_HEIGHT}px); + padding-top: 1.5rem; + padding-bottom: 1.5rem; +`; diff --git a/server/sonar-web/src/main/js/apps/web-api/components/__tests__/Actions-test.tsx b/server/sonar-web/src/main/js/apps/web-api/components/__tests__/Actions-test.tsx index 418d0a4acb8..b4f02541131 100644 --- a/server/sonar-web/src/main/js/apps/web-api/components/__tests__/Actions-test.tsx +++ b/server/sonar-web/src/main/js/apps/web-api/components/__tests__/Actions-test.tsx @@ -85,7 +85,7 @@ it('should allow to browse additional information', async () => { expect(await byText('{"example": "response"}').find()).toBeInTheDocument(); }); -function renderAction(props: Partial<Action['props']> = {}) { +function renderAction(props: Partial<React.ComponentProps<typeof Action>> = {}) { renderComponent( <Action action={mockAction()} @@ -98,7 +98,7 @@ function renderAction(props: Partial<Action['props']> = {}) { } const ui = { - paramsTab: byRole('link', { name: 'api_documentation.parameters' }), - responseExampleTab: byRole('link', { name: 'api_documentation.response_example' }), - changelogTab: byRole('link', { name: 'api_documentation.changelog' }), + paramsTab: byRole('tab', { name: 'api_documentation.parameters' }), + responseExampleTab: byRole('tab', { name: 'api_documentation.response_example' }), + changelogTab: byRole('tab', { name: 'api_documentation.changelog' }), }; diff --git a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx index 4ce3cea9fde..b3a06927a70 100644 --- a/server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx +++ b/server/sonar-web/src/main/js/apps/webhooks/components/DeliveriesForm.tsx @@ -87,7 +87,6 @@ export default function DeliveriesForm({ onClose, webhook }: Props) { loadMore={fetchMoreDeliveries} ready={!loading} total={paging.total} - useMIUIButtons /> )} </Spinner> diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewer.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewer.tsx index ea3a15bda4f..ae22f69629d 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewer.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewer.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { FlagMessage } from 'design-system/lib'; import { intersection } from 'lodash'; import * as React from 'react'; import { @@ -42,7 +43,6 @@ import { SourceLine, SourceViewerFile, } from '../../types/types'; -import { Alert } from '../ui/Alert'; import { WorkspaceContext } from '../workspace/context'; import SourceViewerCode from './SourceViewerCode'; import { SourceViewerContext } from './SourceViewerContext'; @@ -591,17 +591,17 @@ export class SourceViewerClass extends React.PureComponent<Props, State> { if (this.state.notExist) { return ( - <Alert className="spacer-top" variant="warning"> + <FlagMessage className="sw-mt-2" variant="warning"> {translate('component_viewer.no_component')} - </Alert> + </FlagMessage> ); } if (notAccessible) { return ( - <Alert className="spacer-top" variant="warning"> + <FlagMessage className="sw-mt-2" variant="warning"> {translate('code_viewer.no_source_code_displayed_due_to_security')} - </Alert> + </FlagMessage> ); } @@ -615,9 +615,9 @@ export class SourceViewerClass extends React.PureComponent<Props, State> { {!hideHeader && this.renderHeader(component)} {sourceRemoved && ( - <Alert className="spacer-top" variant="warning"> + <FlagMessage className="sw-mt-4 sw-ml-4" variant="warning"> {translate('code_viewer.no_source_code_displayed_due_to_source_removed')} - </Alert> + </FlagMessage> )} {!sourceRemoved && sources !== undefined && this.renderCode(sources)} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/styles.css b/server/sonar-web/src/main/js/components/SourceViewer/styles.css index e978b985050..6c6c0f68c5c 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/styles.css +++ b/server/sonar-web/src/main/js/components/SourceViewer/styles.css @@ -42,10 +42,6 @@ text-align: center; } -.source-viewer-more-code .spinner { - top: -1px; -} - .source-table + .source-viewer-more-code { border-bottom: none; border-top: 1px solid var(--barBorderColor); diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsHistory.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsHistory.tsx index c14197d4c3f..9b51e8a5c14 100644 --- a/server/sonar-web/src/main/js/components/activity-graph/GraphsHistory.tsx +++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsHistory.tsx @@ -73,7 +73,7 @@ export default class GraphsHistory extends React.PureComponent<Props, State> { return ( <div className="sw-flex sw-justify-center sw-flex-col sw-items-stretch sw-grow"> <div className="sw-text-center"> - <Spinner ariaLabel={translate('loading')} loading={loading} /> + <Spinner loading={loading} /> </div> </div> ); diff --git a/server/sonar-web/src/main/js/components/common/ActivityLink.css b/server/sonar-web/src/main/js/components/common/ActivityLink.css deleted file mode 100644 index 52cf39a1024..00000000000 --- a/server/sonar-web/src/main/js/components/common/ActivityLink.css +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -a.activity-link { - border: none; -} - -.activity-link > span { - border-bottom: 1px solid var(--lightBlue); -} diff --git a/server/sonar-web/src/main/js/components/common/ActivityLink.tsx b/server/sonar-web/src/main/js/components/common/ActivityLink.tsx index 5b3b5692cab..0a04807749c 100644 --- a/server/sonar-web/src/main/js/components/common/ActivityLink.tsx +++ b/server/sonar-web/src/main/js/components/common/ActivityLink.tsx @@ -24,7 +24,6 @@ import { getActivityUrl, getMeasureHistoryUrl } from '../../helpers/urls'; import { BranchLike } from '../../types/branch-like'; import { GraphType } from '../../types/project-activity'; import { isCustomGraph } from '../activity-graph/utils'; -import './ActivityLink.css'; export interface ActivityLinkProps { branchLike?: BranchLike; diff --git a/server/sonar-web/src/main/js/components/common/BranchStatus.tsx b/server/sonar-web/src/main/js/components/common/BranchStatus.tsx deleted file mode 100644 index d782cb78f89..00000000000 --- a/server/sonar-web/src/main/js/components/common/BranchStatus.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import Level from '../../components/ui/Level'; -import { BranchLike } from '../../types/branch-like'; - -export interface BranchStatusProps { - branchLike: BranchLike; -} - -export default function BranchStatus(props: BranchStatusProps) { - const { branchLike } = props; - - if (!branchLike.status) { - return null; - } - - return <Level level={branchLike.status.qualityGateStatus} small />; -} diff --git a/server/sonar-web/src/main/js/components/common/CodeSnippet.css b/server/sonar-web/src/main/js/components/common/CodeSnippet.css deleted file mode 100644 index d3396345e70..00000000000 --- a/server/sonar-web/src/main/js/components/common/CodeSnippet.css +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.code-snippet { - background: var(--barBorderColor); - border-radius: 3px; -} - -.code-snippet pre { - padding: var(--gridSize) calc(2 * var(--gridSize)); - border-right: 1px solid var(--transparentGray); - overflow-y: hidden; - overflow-x: auto; -} - -.code-snippet > button { - height: auto; - border: 0; - border-radius: 0; - background: transparent; - padding: var(--gridSize); -} - -.code-snippet > button:hover, -.code-snippet > button:focus, -.code-snippet > button:active { - background-color: var(--transparentGray); - color: var(--darkBlue); -} diff --git a/server/sonar-web/src/main/js/components/common/CodeSnippet.tsx b/server/sonar-web/src/main/js/components/common/CodeSnippet.tsx deleted file mode 100644 index 3169f8b62c7..00000000000 --- a/server/sonar-web/src/main/js/components/common/CodeSnippet.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import * as React from 'react'; -import { ClipboardButton } from '../../components/controls/clipboard'; -import { isDefined } from '../../helpers/types'; -import './CodeSnippet.css'; - -export interface CodeSnippetProps { - isOneLine?: boolean; - noCopy?: boolean; - snippet: string | (string | undefined)[]; -} - -export default function CodeSnippet(props: CodeSnippetProps) { - const { isOneLine, noCopy, snippet } = props; - const snippetRef = React.useRef<HTMLPreElement>(null); - - let finalSnippet: string; - if (Array.isArray(snippet)) { - finalSnippet = snippet.filter((line) => isDefined(line)).join(isOneLine ? ' ' : ' \\\n '); - } else { - finalSnippet = snippet; - } - - return ( - <div className={classNames('code-snippet spacer-top spacer-bottom display-flex-row', {})}> - {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */} - <pre className="flex-1" ref={snippetRef} tabIndex={0}> - {finalSnippet} - </pre> - {!noCopy && <ClipboardButton copyValue={finalSnippet} />} - </div> - ); -} diff --git a/server/sonar-web/src/main/js/components/common/DocLink.tsx b/server/sonar-web/src/main/js/components/common/DocLink.tsx deleted file mode 100644 index bf5b2294a5a..00000000000 --- a/server/sonar-web/src/main/js/components/common/DocLink.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { useDocUrl } from '../../helpers/docs'; -import Link, { LinkProps } from './Link'; - -type Props = Omit<LinkProps, 'to'> & { to: string; innerRef?: React.Ref<HTMLAnchorElement> }; - -/** - * /!\ Deprecated: use DocumentationLink instead - */ -export default function DocLink({ to, innerRef, ...props }: Props) { - const toStatic = useDocUrl(to); - return <Link ref={innerRef} to={toStatic} target="_blank" {...props} />; -} diff --git a/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx b/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx index 7a6cd1b24eb..385f6670f47 100644 --- a/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx +++ b/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx @@ -22,7 +22,7 @@ import * as React from 'react'; import HelpTooltip from '../../components/controls/HelpTooltip'; import { KeyboardKeys } from '../../helpers/keycodes'; import { Placement } from '../controls/Tooltip'; -import DocLink from './DocLink'; +import DocumentationLink from './DocumentationLink'; import Link from './Link'; export interface DocumentationTooltipProps { @@ -79,7 +79,7 @@ export default function DocumentationTooltip(props: DocumentationTooltipProps) { isInteractive innerRef={helpRef} overlay={ - <div className="big-padded-top big-padded-bottom"> + <div className="sw-py-4"> {title && ( <div className="spacer-bottom"> <strong>{title}</strong> @@ -90,14 +90,17 @@ export default function DocumentationTooltip(props: DocumentationTooltipProps) { {links && ( <> - <hr className="big-spacer-top big-spacer-bottom" /> + <hr className="sw-my-4" /> {links.map(({ href, label, inPlace, doc = true }, index) => ( <div className="little-spacer-bottom" key={label}> {doc ? ( - <DocLink to={href} innerRef={(ref) => (linksRef.current[index] = ref)}> + <DocumentationLink + to={href} + innerRef={(ref) => (linksRef.current[index] = ref)} + > {label} - </DocLink> + </DocumentationLink> ) : ( <Link to={href} diff --git a/server/sonar-web/src/main/js/components/common/FiltersHeader.tsx b/server/sonar-web/src/main/js/components/common/FiltersHeader.tsx deleted file mode 100644 index 8a6f4f55192..00000000000 --- a/server/sonar-web/src/main/js/components/common/FiltersHeader.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { Button } from '../../components/controls/buttons'; -import { translate } from '../../helpers/l10n'; - -interface Props { - displayReset: boolean; - onReset: () => void; -} - -export default function FiltersHeader({ displayReset, onReset }: Props) { - return ( - <div className="search-navigator-filters-header"> - {displayReset && ( - <div className="pull-right"> - <Button className="button-red" id="coding-rules-clear-all-filters" onClick={onReset}> - {translate('clear_all_filters')} - </Button> - </div> - )} - - <h2 className="h3">{translate('filters')}</h2> - </div> - ); -} diff --git a/server/sonar-web/src/main/js/components/common/MetaLink.tsx b/server/sonar-web/src/main/js/components/common/MetaLink.tsx index b4df193db66..86ecb5ce07c 100644 --- a/server/sonar-web/src/main/js/components/common/MetaLink.tsx +++ b/server/sonar-web/src/main/js/components/common/MetaLink.tsx @@ -55,7 +55,7 @@ export default function MetaLink({ iconOnly, link }: Readonly<Props>) { to={link.url} preventDefault={!isValid} onClick={isValid ? undefined : handleClick} - icon={<ProjectLinkIcon miui type={link.type} />} + icon={<ProjectLinkIcon type={link.type} />} > {!iconOnly && linkTitle} </StyledLink> diff --git a/server/sonar-web/src/main/js/components/common/MultiSelect.tsx b/server/sonar-web/src/main/js/components/common/MultiSelect.tsx deleted file mode 100644 index e4fa098d3a4..00000000000 --- a/server/sonar-web/src/main/js/components/common/MultiSelect.tsx +++ /dev/null @@ -1,234 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import { isEmpty, remove, xor } from 'lodash'; -import * as React from 'react'; -import SearchBox from '../../components/controls/SearchBox'; -import { translateWithParameters } from '../../helpers/l10n'; -import MultiSelectOption, { Element } from './MultiSelectOption'; - -export interface MultiSelectProps { - allowNewElements?: boolean; - allowSelection?: boolean; - legend: string; - elements: string[]; - filterSelected?: (query: string, selectedElements: string[]) => string[]; - footerNode?: React.ReactNode; - listSize?: number; - onSearch: (query: string) => Promise<void>; - onSelect: (item: string) => void; - onUnselect: (item: string) => void; - placeholder: string; - renderLabel?: (element: string) => React.ReactNode; - selectedElements: string[]; - validateSearchInput?: (value: string) => string; -} - -interface State { - loading: boolean; - query: string; - elements: Element[]; -} - -interface DefaultProps { - // eslint-disable-next-line react/no-unused-prop-types - filterSelected: (query: string, selectedElements: string[]) => string[]; - listSize: number; - renderLabel: (element: string) => React.ReactNode; - validateSearchInput: (value: string) => string; -} - -type PropsWithDefault = MultiSelectProps & DefaultProps; - -export default class MultiSelect extends React.PureComponent<PropsWithDefault, State> { - mounted = false; - - static defaultProps: DefaultProps = { - filterSelected: (query: string, selectedElements: string[]) => - selectedElements.filter((elem) => elem.includes(query)), - listSize: 0, - renderLabel: (element: string) => element, - validateSearchInput: (value: string) => value, - }; - - constructor(props: PropsWithDefault) { - super(props); - this.state = { - loading: false, - query: '', - elements: [], - }; - } - - componentDidMount() { - this.mounted = true; - this.onSearchQuery(''); - this.computeElements(); - } - - componentDidUpdate(prevProps: PropsWithDefault, prevState: State) { - if ( - !isEmpty( - xor( - [...prevProps.selectedElements, ...prevProps.elements], - [...this.props.selectedElements, ...this.props.elements], - ), - ) - ) { - this.computeElements(); - } - - if (prevState.query !== this.state.query) { - this.setState(({ query, elements }, props) => { - const newElements = [...elements]; - this.appendCreateElelement(newElements, query, props); - return { elements: newElements }; - }); - } - } - - componentWillUnmount() { - this.mounted = false; - } - - computeElements() { - this.setState(({ query }, props) => { - const newStateElement: Element[] = [ - ...this.props - .filterSelected(query, this.props.selectedElements) - .map((e) => ({ value: e, selected: true })), - ...this.props.elements.map((e) => ({ - value: e, - selected: false, - })), - ]; - - this.appendCreateElelement(newStateElement, query, props); - return { elements: newStateElement }; - }); - } - - appendCreateElelement(elements: Element[], query: string, props: PropsWithDefault) { - const { allowNewElements = true } = props; - if (this.isNewElement(query, props) && allowNewElements) { - const create = elements.find((e) => e.custom); - if (create) { - create.value = query; - } else { - elements.push({ value: query, selected: false, custom: true }); - } - } else if (!this.isNewElement(query, props) && allowNewElements) { - remove(elements, (e) => e.custom); - } - } - - handleSelectChange = (selected: boolean, item: string) => { - this.setState(({ elements }) => { - const newElements = elements.map((e) => - e.value === item ? { value: e.value, selected } : e, - ); - return { elements: newElements }; - }); - if (selected) { - this.onSelectItem(item); - } else { - this.onUnselectItem(item); - } - }; - - handleSearchChange = (value: string) => { - this.onSearchQuery((this.props as PropsWithDefault).validateSearchInput(value)); - }; - - onSearchQuery = (query: string) => { - const { allowNewElements = true } = this.props; - - this.props.onSearch(query).then(this.stopLoading, this.stopLoading); - if (allowNewElements) { - this.setState({ - loading: true, - query, - }); - } - }; - - onSelectItem = (item: string) => { - if (this.isNewElement(item, this.props)) { - this.onSearchQuery(''); - } - this.props.onSelect(item); - }; - - onUnselectItem = (item: string) => this.props.onUnselect(item); - - isNewElement = (elem: string, { selectedElements, elements }: PropsWithDefault) => - !isEmpty(elem) && selectedElements.indexOf(elem) === -1 && elements.indexOf(elem) === -1; - - stopLoading = () => { - if (this.mounted) { - this.setState({ loading: false }); - } - }; - - render() { - const { legend, allowSelection = true, footerNode = '' } = this.props; - const { renderLabel } = this.props as PropsWithDefault; - const { query, elements } = this.state; - const infiniteList = this.props.listSize === 0; - const listClasses = classNames('menu', { - 'menu-vertically-limited': infiniteList, - 'spacer-top': infiniteList, - 'with-top-separator': infiniteList, - 'with-bottom-separator': Boolean(footerNode), - }); - - return ( - <div className="multi-select"> - <div className="menu-search"> - <SearchBox - autoFocus - className="little-spacer-top" - loading={this.state.loading} - onChange={this.handleSearchChange} - placeholder={this.props.placeholder} - value={query} - /> - </div> - <fieldset aria-label={legend}> - <ul className={listClasses}> - {elements.map((e) => ( - <MultiSelectOption - element={e} - disabled={!allowSelection && !e.selected} - key={e.value} - onSelectChange={this.handleSelectChange} - renderLabel={renderLabel} - /> - ))} - {isEmpty(elements) && ( - <li className="spacer-left">{translateWithParameters('no_results_for_x', query)}</li> - )} - </ul> - </fieldset> - {footerNode} - </div> - ); - } -} diff --git a/server/sonar-web/src/main/js/components/common/MultiSelectOption.tsx b/server/sonar-web/src/main/js/components/common/MultiSelectOption.tsx deleted file mode 100644 index f9cea98cf22..00000000000 --- a/server/sonar-web/src/main/js/components/common/MultiSelectOption.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import * as React from 'react'; -import Checkbox from '../../components/controls/Checkbox'; -import { translate } from '../../helpers/l10n'; - -export interface Element { - value: string; - selected: boolean; - custom?: boolean; -} - -export interface MultiSelectOptionProps { - disabled?: boolean; - element: Element; - onSelectChange: (selected: boolean, element: string) => void; - renderLabel: (element: string) => React.ReactNode; -} - -export default function MultiSelectOption(props: MultiSelectOptionProps) { - const { disabled, element } = props; - const [active, setActive] = React.useState(false); - const className = classNames({ active, disabled }); - const label = props.renderLabel(element.value); - - return ( - <li - onFocus={() => setActive(true)} - onBlur={() => setActive(false)} - onMouseLeave={() => setActive(false)} - onMouseOver={() => setActive(true)} - > - <Checkbox - checked={element.selected} - className={className} - disabled={disabled} - id={element.value} - onCheck={props.onSelectChange} - > - {element.custom ? ( - <span - aria-label={`${translate('create_new_element')}: ${label}`} - className="little-spacer-left" - > - <span aria-hidden className="little-spacer-right"> - + - </span> - {label} - </span> - ) : ( - <span className="little-spacer-left">{label}</span> - )} - </Checkbox> - </li> - ); -} diff --git a/server/sonar-web/src/main/js/components/common/StatusIndicator.css b/server/sonar-web/src/main/js/components/common/StatusIndicator.css deleted file mode 100644 index e116fd9da4f..00000000000 --- a/server/sonar-web/src/main/js/components/common/StatusIndicator.css +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.status-indicator { - display: inline-block; - box-sizing: border-box; - width: 16px; - height: 16px; - border-radius: 16px; - margin: 4px; -} - -.status-indicator.small-status-indicator { - width: 8px; - height: 8px; - border-radius: 8px; - margin: 8px; -} - -.status-indicator.big-status-indicator { - width: var(--controlHeight); - height: var(--controlHeight); - border-radius: var(--controlHeight); - margin: 0; -} - -.status-indicator.red { - position: relative; - z-index: 2; - background-color: var(--red); -} - -.status-indicator.yellow { - background-color: var(--yellow); -} - -.status-indicator.green { - background-color: var(--green); -} - -.status-indicator.orange { - background-color: var(--orange); -} - -.status-indicator.gray { - background-color: var(--gray71); -} diff --git a/server/sonar-web/src/main/js/components/common/StatusIndicator.tsx b/server/sonar-web/src/main/js/components/common/StatusIndicator.tsx index c6cac3e2379..a7d6091a509 100644 --- a/server/sonar-web/src/main/js/components/common/StatusIndicator.tsx +++ b/server/sonar-web/src/main/js/components/common/StatusIndicator.tsx @@ -21,7 +21,6 @@ import { CheckIcon, FlagErrorIcon, FlagWarningIcon } from 'design-system'; import * as React from 'react'; import { translate } from '../../helpers/l10n'; import { HealthTypes } from '../../types/types'; -import './StatusIndicator.css'; export interface StatusIndicatorProps { color: HealthTypes; diff --git a/server/sonar-web/src/main/js/components/common/VisibilitySelector.tsx b/server/sonar-web/src/main/js/components/common/VisibilitySelector.tsx index 0a52aa93bd3..4a71ec2eb19 100644 --- a/server/sonar-web/src/main/js/components/common/VisibilitySelector.tsx +++ b/server/sonar-web/src/main/js/components/common/VisibilitySelector.tsx @@ -39,7 +39,7 @@ export default function VisibilitySelector(props: VisibilitySelectorProps) { <div className={classNames(className)}> {Object.values(Visibility).map((v) => ( <RadioButton - className={`huge-spacer-right visibility-${v}`} + className={`sw-mr-10 it__visibility-${v}`} key={v} value={v} checked={v === visibility} diff --git a/server/sonar-web/src/main/js/components/common/__tests__/BranchStatus-test.tsx b/server/sonar-web/src/main/js/components/common/__tests__/BranchStatus-test.tsx deleted file mode 100644 index 114c58b37cd..00000000000 --- a/server/sonar-web/src/main/js/components/common/__tests__/BranchStatus-test.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { screen } from '@testing-library/react'; -import * as React from 'react'; -import BranchesServiceMock from '../../../api/mocks/BranchesServiceMock'; -import { mockBranch } from '../../../helpers/mocks/branch-like'; -import { renderComponent } from '../../../helpers/testReactTestingUtils'; -import BranchStatus, { BranchStatusProps } from '../BranchStatus'; - -const handler = new BranchesServiceMock(); - -beforeEach(() => { - handler.reset(); -}); - -it('should render ok status', async () => { - renderBranchStatus({ branchLike: mockBranch({ status: { qualityGateStatus: 'OK' } }) }); - - expect(await screen.findByText('OK')).toBeInTheDocument(); -}); - -it('should render error status', async () => { - renderBranchStatus({ branchLike: mockBranch({ status: { qualityGateStatus: 'ERROR' } }) }); - - expect(await screen.findByText('ERROR')).toBeInTheDocument(); -}); - -function renderBranchStatus(overrides: Partial<BranchStatusProps> = {}) { - const defaultProps = { - branchLike: mockBranch(), - } as const; - return renderComponent(<BranchStatus {...defaultProps} {...overrides} />); -} diff --git a/server/sonar-web/src/main/js/components/common/__tests__/DocumentationTooltip-test.tsx b/server/sonar-web/src/main/js/components/common/__tests__/DocumentationTooltip-test.tsx index f5ee4566e9f..d6a721b4719 100644 --- a/server/sonar-web/src/main/js/components/common/__tests__/DocumentationTooltip-test.tsx +++ b/server/sonar-web/src/main/js/components/common/__tests__/DocumentationTooltip-test.tsx @@ -31,7 +31,7 @@ const ui = { helpIcon: byTestId('help-tooltip-activator'), helpLink: byRole('link', { name: 'Icon' }), linkInTooltip: byRole('link', { name: 'Label' }), - linkInTooltip2: byRole('link', { name: 'opens_in_new_window Label2' }), + linkInTooltip2: byRole('link', { name: 'Label2' }), afterLink: byRole('link', { name: 'Interactive element after' }), }; diff --git a/server/sonar-web/src/main/js/components/common/__tests__/MultiSelect-test.tsx b/server/sonar-web/src/main/js/components/common/__tests__/MultiSelect-test.tsx deleted file mode 100644 index 0e9e234137f..00000000000 --- a/server/sonar-web/src/main/js/components/common/__tests__/MultiSelect-test.tsx +++ /dev/null @@ -1,121 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { render } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import * as React from 'react'; -import { byRole, byText } from '../../../helpers/testSelector'; -import MultiSelect from '../MultiSelect'; - -const ui = { - checkbox: (name: string) => byRole('checkbox', { name }), - search: byRole('searchbox', { name: 'search' }), - noResult: byText('no_results_for_x.notfound'), -}; - -it('should handle selection', async () => { - const user = userEvent.setup(); - const rerender = renderMultiSelect(); - expect(ui.checkbox('az').get()).toBeChecked(); - await user.click(ui.checkbox('az').get()); - expect(ui.checkbox('az').get()).not.toBeChecked(); - - await user.type(ui.search.get(), 'create'); - await user.click(ui.checkbox('create_new_element: create').get()); - expect(ui.checkbox('create').get()).toBeChecked(); - - // Custom label - rerender({ renderLabel: (label) => `prefxed-${label}` }); - expect(ui.checkbox('prefxed-create').get()).toBeChecked(); -}); - -it('should handle disable selection', () => { - renderMultiSelect({ allowSelection: false }); - expect(ui.checkbox('az').get()).toBeChecked(); - expect(ui.checkbox('cx').get()).toHaveAttribute('aria-disabled', 'true'); -}); - -it('should handle search', async () => { - const user = userEvent.setup(); - const rerender = renderMultiSelect(); - expect(ui.checkbox('cx').get()).toBeInTheDocument(); - await user.type(ui.search.get(), 'az'); - expect(ui.checkbox('cx').query()).not.toBeInTheDocument(); - expect(ui.checkbox('az').get()).toBeInTheDocument(); - expect(ui.checkbox('az-new').get()).toBeInTheDocument(); - - await user.clear(ui.search.get()); - await user.type(ui.search.get(), 'notfound'); - expect(ui.checkbox('create_new_element: notfound').get()).toBeInTheDocument(); - - rerender({ allowNewElements: false }); - await user.clear(ui.search.get()); - await user.type(ui.search.get(), 'notfound'); - expect(ui.noResult.get()).toBeInTheDocument(); -}); - -function renderMultiSelect(override?: Partial<MultiSelect['props']>) { - const initial = ['cx', 'dw', 'ev', 'fu', 'gt', 'hs']; - const initialSelected = ['az', 'by']; - - function Parent(props?: Partial<MultiSelect['props']>) { - const [elements, setElements] = React.useState(initial); - const [selected, setSelected] = React.useState(['az', 'by']); - const onSearch = (query: string) => { - if (query === 'notfound') { - setElements([]); - setSelected([]); - } else if (query === '') { - setElements(initial); - setSelected(initialSelected); - } else { - setElements([...elements.filter((e) => e.indexOf(query) !== -1), `${query}-new`]); - setSelected(selected.filter((e) => e.indexOf(query) !== -1)); - } - return Promise.resolve(); - }; - - const onSelect = (element: string) => { - setSelected([...selected, element]); - setElements(elements.filter((e) => e !== element)); - }; - - const onUnselect = (element: string) => { - setElements([...elements, element]); - setSelected(selected.filter((e) => e !== element)); - }; - return ( - <MultiSelect - selectedElements={selected} - elements={elements} - legend="multi select" - onSearch={onSearch} - onSelect={onSelect} - onUnselect={onUnselect} - placeholder="search" - {...props} - /> - ); - } - - const { rerender } = render(<Parent {...override} />); - return function (reoverride?: Partial<MultiSelect['props']>) { - rerender(<Parent {...override} {...reoverride} />); - }; -} diff --git a/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx b/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx deleted file mode 100644 index 28e5987cb72..00000000000 --- a/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx +++ /dev/null @@ -1,143 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import * as React from 'react'; -import { To } from 'react-router-dom'; -import Link from '../common/Link'; -import DropdownIcon from '../icons/DropdownIcon'; -import SettingsIcon from '../icons/SettingsIcon'; -import { PopupPlacement } from '../ui/popups'; -import Dropdown from './Dropdown'; -import Tooltip, { Placement } from './Tooltip'; -import { Button, ButtonPlain } from './buttons'; - -export interface ActionsDropdownProps { - className?: string; - children: React.ReactNode; - label?: string; - onOpen?: () => void; - overlayPlacement?: PopupPlacement; - small?: boolean; - toggleClassName?: string; - disabled?: boolean; -} - -export default function ActionsDropdown(props: ActionsDropdownProps) { - const { children, className, label, overlayPlacement, small, toggleClassName } = props; - return ( - <Dropdown - className={className} - onOpen={props.onOpen} - overlay={<ul className="menu">{children}</ul>} - overlayPlacement={overlayPlacement} - > - <Button - aria-label={label} - className={classNames('dropdown-toggle', toggleClassName, { - 'button-small': small, - })} - disabled={props.disabled} - > - <SettingsIcon size={small ? 12 : 14} /> - <DropdownIcon className="little-spacer-left" /> - </Button> - </Dropdown> - ); -} - -interface ItemProps { - className?: string; - children: React.ReactNode; - destructive?: boolean; - disabled?: boolean; - label?: string; - tooltipOverlay?: React.ReactNode; - tooltipPlacement?: Placement; - /** used to pass a name of downloaded file */ - download?: string; - id?: string; - onClick?: () => void; - to?: To; -} - -export class ActionsDropdownItem extends React.PureComponent<ItemProps> { - handleClick = (event?: React.SyntheticEvent<HTMLAnchorElement>) => { - if (event) { - event.preventDefault(); - event.currentTarget.blur(); - } - if (this.props.onClick) { - this.props.onClick(); - } - }; - - render() { - const className = classNames(this.props.className, { 'text-danger': this.props.destructive }); - let { children } = this.props; - const { tooltipOverlay, tooltipPlacement, label } = this.props; - - if (this.props.download && typeof this.props.to === 'string') { - children = ( - <a - className={className} - aria-label={label} - download={this.props.download} - href={this.props.to} - id={this.props.id} - > - {children} - </a> - ); - } else if (this.props.to) { - children = ( - <Link className={className} id={this.props.id} to={this.props.to} aria-label={label}> - {children} - </Link> - ); - } else { - children = ( - <ButtonPlain - className={className} - disabled={this.props.disabled} - preventDefault - id={this.props.id} - onClick={this.handleClick} - aria-label={label} - > - {children} - </ButtonPlain> - ); - } - - if (tooltipOverlay !== undefined) { - return ( - <Tooltip overlay={tooltipOverlay} placement={tooltipPlacement}> - <li>{children}</li> - </Tooltip> - ); - } - - return <li>{children}</li>; - } -} - -export function ActionsDropdownDivider() { - return <li className="divider" />; -} diff --git a/server/sonar-web/src/main/js/components/controls/BoxedGroupAccordion.tsx b/server/sonar-web/src/main/js/components/controls/BoxedGroupAccordion.tsx deleted file mode 100644 index c8e14b63984..00000000000 --- a/server/sonar-web/src/main/js/components/controls/BoxedGroupAccordion.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import { uniqueId } from 'lodash'; -import * as React from 'react'; -import OpenCloseIcon from '../icons/OpenCloseIcon'; -import { ButtonPlain } from './buttons'; - -interface BoxedGroupAccordionProps { - children: React.ReactNode; - noBorder?: boolean; - className?: string; - data?: string; - onClick: (data?: string) => void; - open: boolean; - renderHeader?: () => React.ReactNode; - title: React.ReactNode; -} - -export default function BoxedGroupAccordion(props: BoxedGroupAccordionProps) { - const { className, noBorder, open, renderHeader, title, data, onClick } = props; - - const id = React.useMemo(() => uniqueId('accordion-'), []); - const handleClick = React.useCallback(() => { - onClick(data); - }, [onClick, data]); - - return ( - <div - className={classNames('boxed-group boxed-group-accordion', className, { - 'no-border': noBorder, - open, - })} - role="listitem" - > - {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} - <div onClick={handleClick} className="display-flex-center boxed-group-header"> - <ButtonPlain - stopPropagation - className="boxed-group-accordion-title flex-grow" - onClick={handleClick} - id={`${id}-header`} - aria-controls={`${id}-panel`} - aria-expanded={open} - > - {title} - </ButtonPlain> - {renderHeader && renderHeader()} - <OpenCloseIcon aria-hidden className="spacer-left" open={open} /> - </div> - <div id={`${id}-panel`} aria-labelledby={`${id}-header`} role="region"> - {open && <div className="boxed-group-inner">{props.children}</div>} - </div> - </div> - ); -} diff --git a/server/sonar-web/src/main/js/components/controls/BoxedTabs.tsx b/server/sonar-web/src/main/js/components/controls/BoxedTabs.tsx deleted file mode 100644 index e53321cd490..00000000000 --- a/server/sonar-web/src/main/js/components/controls/BoxedTabs.tsx +++ /dev/null @@ -1,105 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import styled from '@emotion/styled'; -import * as React from 'react'; -import { colors, sizes } from '../../app/theme'; - -export interface BoxedTabsProps<K> { - className?: string; - onSelect: (key: K) => void; - selected?: K; - tabs: ReadonlyArray<{ key: K; label: React.ReactNode }>; -} - -const TabContainer = styled.div` - display: flex; - flex-direction: row; -`; - -const baseBorder = () => `1px solid ${colors.barBorderColor}`; - -const highlightHoverMixin = () => ` - &:hover { - background-color: ${colors.barBackgroundColorHighlight}; - } -`; - -const StyledTab = styled.button<{ active: boolean }>` - position: relative; - background-color: ${(props) => (props.active ? 'white' : colors.barBackgroundColor)}; - border-top: ${baseBorder}; - border-left: ${baseBorder}; - border-right: none; - border-bottom: none; - margin-bottom: -1px; - min-width: 128px; - min-height: 56px; - ${(props) => !props.active && 'cursor: pointer;'} - outline: 0; - padding: calc(2 * ${sizes.gridSize}); - - ${(props) => (!props.active ? highlightHoverMixin : null)} - - &:last-child { - border-right: ${baseBorder}; - } -`; - -const ActiveBorder = styled.div<{ active: boolean }>` - display: ${(props) => (props.active ? 'block' : 'none')}; - background-color: ${colors.blue}; - height: 3px; - width: 100%; - position: absolute; - left: 0; - top: -1px; -`; - -export default function BoxedTabs<K>(props: BoxedTabsProps<K>) { - const { className, tabs, selected } = props; - - return ( - <TabContainer className={className} role="tablist"> - {tabs.map(({ key, label }, i) => ( - <StyledTab - id={getTabId(key)} - active={selected === key} - aria-selected={selected === key} - aria-controls={getTabPanelId(key)} - // eslint-disable-next-line react/no-array-index-key - key={i} - onClick={() => selected !== key && props.onSelect(key)} - role="tab" - > - <ActiveBorder active={selected === key} /> - {label} - </StyledTab> - ))} - </TabContainer> - ); -} - -export function getTabPanelId<K>(key: K) { - return `tabpanel-${key}`; -} - -export function getTabId<K>(key: K) { - return `tab-${key}`; -} diff --git a/server/sonar-web/src/main/js/components/controls/ButtonToggle.css b/server/sonar-web/src/main/js/components/controls/ButtonToggle.css deleted file mode 100644 index 1c2e00f07fe..00000000000 --- a/server/sonar-web/src/main/js/components/controls/ButtonToggle.css +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.button-toggle { - display: flex; - list-style: none !important; -} - -.button-toggle li:not(:first-child) { - margin-left: -1px; -} diff --git a/server/sonar-web/src/main/js/components/controls/ButtonToggle.tsx b/server/sonar-web/src/main/js/components/controls/ButtonToggle.tsx deleted file mode 100644 index bfcfdf8140a..00000000000 --- a/server/sonar-web/src/main/js/components/controls/ButtonToggle.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import * as React from 'react'; -import { Button } from './buttons'; -import './ButtonToggle.css'; - -export type ButtonToggleValueType = string | number | boolean; - -export interface ButtonToggleOption { - label: string; - value: ButtonToggleValueType; -} - -export interface ButtonToggleProps { - label?: string; - disabled?: boolean; - options: ButtonToggleOption[]; - value?: ButtonToggleValueType; - onCheck: (value: ButtonToggleValueType) => void; -} - -export default function ButtonToggle(props: ButtonToggleProps) { - const { disabled, label, options, value } = props; - - return ( - <ul aria-label={label} className="button-toggle"> - {options.map((option) => ( - <li key={option.value.toString()}> - <Button - onClick={() => option.value !== value && props.onCheck(option.value)} - disabled={disabled} - aria-current={option.value === value} - data-value={option.value} - className={classNames({ selected: option.value === value })} - > - {option.label} - </Button> - </li> - ))} - </ul> - ); -} diff --git a/server/sonar-web/src/main/js/components/controls/Checkbox.css b/server/sonar-web/src/main/js/components/controls/Checkbox.css deleted file mode 100644 index 982bc8bcbd3..00000000000 --- a/server/sonar-web/src/main/js/components/controls/Checkbox.css +++ /dev/null @@ -1,94 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.icon-checkbox { - display: inline-block; - line-height: 1; - vertical-align: top; - padding: 1px 2px; - box-sizing: border-box; -} - -a.icon-checkbox { - border-bottom: none; -} - -.icon-checkbox:focus { - outline: none !important; -} - -.icon-checkbox:before { - content: ' '; - display: inline-block; - width: 10px; - height: 10px; - border: 1px solid var(--darkBlue); - border-radius: 2px; - transition: - border-color 0.2s ease, - background-color 0.2s ease, - background-image 0.2s ease, - box-shadow 0.4s ease; -} - -.icon-checkbox:not(.icon-checkbox-disabled):focus:before, -.link-checkbox:not(.disabled):focus:focus .icon-checkbox:before { - box-shadow: 0 0 0 3px rgba(35, 106, 151, 0.25); -} - -.icon-checkbox-checked:before { - background-color: var(--blue); - background-image: url('data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2014%2014%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20stroke-linejoin%3D%22round%22%20stroke-miterlimit%3D%221.414%22%3E%3Cpath%20d%3D%22M12%204.665c0%20.172-.06.318-.18.438l-5.55%205.55c-.12.12-.266.18-.438.18s-.318-.06-.438-.18L2.18%207.438C2.06%207.317%202%207.17%202%207s.06-.318.18-.44l.878-.876c.12-.12.267-.18.44-.18.17%200%20.317.06.437.18l1.897%201.903%204.233-4.24c.12-.12.266-.18.438-.18s.32.06.44.18l.876.88c.12.12.18.265.18.438z%22%20fill%3D%22%23fff%22%20fill-rule%3D%22nonzero%22%2F%3E%3C%2Fsvg%3E'); - border-color: var(--blue); -} - -.icon-checkbox-checked.icon-checkbox-single:before { - background-image: url('data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2014%2014%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20stroke-linejoin%3D%22round%22%20stroke-miterlimit%3D%221.414%22%3E%3Cpath%20d%3D%22M10%204.698C10%204.312%209.688%204%209.302%204H4.698C4.312%204%204%204.312%204%204.698v4.604c0%20.386.312.698.698.698h4.604c.386%200%20.698-.312.698-.698V4.698z%22%20fill%3D%22%23fff%22%2F%3E%3C%2Fsvg%3E'); -} - -.icon-checkbox-disabled:before { - border: 1px solid var(--disableGrayText); - cursor: not-allowed; -} - -.icon-checkbox-disabled.icon-checkbox-checked:before { - background-color: var(--disableGrayText); -} - -.icon-checkbox-invisible { - visibility: hidden; -} - -.link-checkbox { - color: inherit; - border-bottom: none; -} - -.link-checkbox.disabled, -.link-checkbox.disabled:hover, -.link-checkbox.disabled label { - color: var(--secondFontColor); - cursor: not-allowed; -} - -.link-checkbox:hover, -.link-checkbox:active, -.link-checkbox:focus { - color: inherit; -} diff --git a/server/sonar-web/src/main/js/components/controls/Checkbox.tsx b/server/sonar-web/src/main/js/components/controls/Checkbox.tsx deleted file mode 100644 index f98878c66bf..00000000000 --- a/server/sonar-web/src/main/js/components/controls/Checkbox.tsx +++ /dev/null @@ -1,106 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -/* eslint-disable jsx-a11y/anchor-has-content */ -/* eslint-disable jsx-a11y/control-has-associated-label */ - -import classNames from 'classnames'; -import * as React from 'react'; -import Spinner from '../ui/Spinner'; -import './Checkbox.css'; - -interface Props { - checked: boolean; - disabled?: boolean; - children?: React.ReactNode; - className?: string; - id?: string; - label?: string; - loading?: boolean; - onCheck: (checked: boolean, id?: string) => void; - right?: boolean; - thirdState?: boolean; - title?: string; -} - -export default class Checkbox extends React.PureComponent<Props> { - static defaultProps = { - thirdState: false, - }; - - handleClick = (event: React.SyntheticEvent<HTMLElement>) => { - event.preventDefault(); - event.currentTarget.blur(); - if (!this.props.disabled) { - this.props.onCheck(!this.props.checked, this.props.id); - } - }; - - render() { - const { checked, children, disabled, id, label, loading, right, thirdState, title } = - this.props; - const className = classNames('icon-checkbox', { - 'icon-checkbox-checked': checked, - 'icon-checkbox-single': thirdState, - 'icon-checkbox-disabled': disabled, - }); - - if (children) { - return ( - <a - aria-checked={thirdState ? 'mixed' : checked} - aria-label={label} - aria-disabled={disabled} - className={classNames('link-checkbox', this.props.className, { - disabled, - })} - href="#" - id={id} - onClick={this.handleClick} - role="checkbox" - title={title} - > - {right && children} - <Spinner loading={Boolean(loading)}> - <i className={className} /> - </Spinner> - {!right && children} - </a> - ); - } - - if (loading) { - return <Spinner ariaLabel={label} />; - } - - return ( - <a - aria-checked={thirdState ? 'mixed' : checked} - aria-label={label} - aria-disabled={disabled} - className={classNames(className, this.props.className)} - href="#" - id={id} - onClick={this.handleClick} - role="checkbox" - title={title} - /> - ); - } -} diff --git a/server/sonar-web/src/main/js/components/controls/Dropdown.css b/server/sonar-web/src/main/js/components/controls/Dropdown.css deleted file mode 100644 index 0bf8a21f41d..00000000000 --- a/server/sonar-web/src/main/js/components/controls/Dropdown.css +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.dropdown { - position: relative; - display: inline-block; - vertical-align: middle; -} - -.dropdown-bottom-hint { - line-height: 16px; - margin-bottom: -5px; - padding: 5px 10px; - border-top: 1px solid var(--barBorderColor); - background-color: var(--barBackgroundColor); - color: var(--secondFontColor); - font-size: 11px; -} diff --git a/server/sonar-web/src/main/js/components/controls/Dropdown.tsx b/server/sonar-web/src/main/js/components/controls/Dropdown.tsx index 4388f81add9..96cf2efb049 100644 --- a/server/sonar-web/src/main/js/components/controls/Dropdown.tsx +++ b/server/sonar-web/src/main/js/components/controls/Dropdown.tsx @@ -17,103 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import classNames from 'classnames'; import * as React from 'react'; import { Popup, PopupPlacement } from '../ui/popups'; -import './Dropdown.css'; import ScreenPositionFixer from './ScreenPositionFixer'; -import Toggler from './Toggler'; - -interface OnClickCallback { - (event?: React.SyntheticEvent<HTMLElement>): void; -} - -interface RenderProps { - closeDropdown: () => void; - onToggleClick: OnClickCallback; - open: boolean; -} - -interface Props { - children: - | ((renderProps: RenderProps) => JSX.Element) - | React.ReactElement<{ onClick: OnClickCallback }>; - className?: string; - closeOnClick?: boolean; - closeOnClickOutside?: boolean; - onOpen?: () => void; - overlay: React.ReactNode; - overlayPlacement?: PopupPlacement; - noOverlayPadding?: boolean; - tagName?: string; -} - -interface State { - open: boolean; -} - -export default class Dropdown extends React.PureComponent<Props, State> { - state: State = { open: false }; - - componentDidUpdate(_: Props, prevState: State) { - if (!prevState.open && this.state.open && this.props.onOpen) { - this.props.onOpen(); - } - } - - closeDropdown = () => this.setState({ open: false }); - - handleToggleClick = (event?: React.SyntheticEvent<HTMLElement>) => { - if (event) { - event.preventDefault(); - event.currentTarget.blur(); - } - this.setState((state) => ({ open: !state.open })); - }; - - render() { - const a11yAttrs = { - 'aria-expanded': String(this.state.open), - 'aria-haspopup': 'true', - }; - - const child = React.isValidElement(this.props.children) - ? React.cloneElement(this.props.children, { onClick: this.handleToggleClick, ...a11yAttrs }) - : this.props.children({ - closeDropdown: this.closeDropdown, - onToggleClick: this.handleToggleClick, - open: this.state.open, - }); - - const { closeOnClick = true, closeOnClickOutside = false } = this.props; - - const toggler = ( - <Toggler - closeOnClick={closeOnClick} - closeOnClickOutside={closeOnClickOutside} - onRequestClose={this.closeDropdown} - open={this.state.open} - overlay={ - <DropdownOverlay - noPadding={this.props.noOverlayPadding} - placement={this.props.overlayPlacement} - useEventBoundary={!closeOnClick} - > - {this.props.overlay} - </DropdownOverlay> - } - > - {child} - </Toggler> - ); - - return React.createElement( - this.props.tagName || 'div', - { className: classNames('dropdown', this.props.className) }, - toggler, - ); - } -} interface OverlayProps { className?: string; diff --git a/server/sonar-web/src/main/js/components/controls/HelpTooltip.css b/server/sonar-web/src/main/js/components/controls/HelpTooltip.css deleted file mode 100644 index 68ec76d3b06..00000000000 --- a/server/sonar-web/src/main/js/components/controls/HelpTooltip.css +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.help-tooltip { - display: inline-flex; - align-items: center; - vertical-align: middle; -} - -.help-toolip-link { - display: block; - width: 12px; - height: 12px; - border: none; -} diff --git a/server/sonar-web/src/main/js/components/controls/HelpTooltip.tsx b/server/sonar-web/src/main/js/components/controls/HelpTooltip.tsx index 3dc45af16c8..1105fef9a03 100644 --- a/server/sonar-web/src/main/js/components/controls/HelpTooltip.tsx +++ b/server/sonar-web/src/main/js/components/controls/HelpTooltip.tsx @@ -18,15 +18,12 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import classNames from 'classnames'; +import { HelperHintIcon } from 'design-system'; import * as React from 'react'; -import { colors } from '../../app/theme'; import { translate } from '../../helpers/l10n'; -import HelpIcon from '../icons/HelpIcon'; -import { IconProps } from '../icons/Icon'; -import './HelpTooltip.css'; import Tooltip, { Placement } from './Tooltip'; -interface Props extends Pick<IconProps, 'size'> { +interface Props { className?: string; children?: React.ReactNode; onShow?: () => void; @@ -35,6 +32,7 @@ interface Props extends Pick<IconProps, 'size'> { placement?: Placement; isInteractive?: boolean; innerRef?: React.Ref<HTMLSpanElement>; + size?: number; } const DEFAULT_SIZE = 12; @@ -42,7 +40,12 @@ const DEFAULT_SIZE = 12; export default function HelpTooltip(props: Props) { const { size = DEFAULT_SIZE, overlay, placement, isInteractive, innerRef, children } = props; return ( - <div className={classNames('help-tooltip', props.className)}> + <div + className={classNames( + 'it__help-tooltip sw-inline-flex sw-items-center sw-align-middle', + props.className, + )} + > <Tooltip mouseLeaveDelay={0.25} onShow={props.onShow} @@ -52,15 +55,12 @@ export default function HelpTooltip(props: Props) { isInteractive={isInteractive} > <span - className="display-inline-flex-center" + className="sw-inline-flex sw-items-center" data-testid="help-tooltip-activator" ref={innerRef} > {children ?? ( - <HelpIcon - fill={colors.gray60} - size={size} - role="img" + <HelperHintIcon aria-label={isInteractive ? translate('tooltip_is_interactive') : translate('help')} description={ isInteractive ? ( @@ -72,6 +72,8 @@ export default function HelpTooltip(props: Props) { overlay ) } + height={size} + width={size} /> )} </span> @@ -79,11 +81,3 @@ export default function HelpTooltip(props: Props) { </div> ); } - -export function DarkHelpTooltip({ size = DEFAULT_SIZE, ...props }: Omit<Props, 'children'>) { - return ( - <HelpTooltip {...props}> - <HelpIcon fill={colors.transparentBlack} fillInner={colors.white} size={size} /> - </HelpTooltip> - ); -} diff --git a/server/sonar-web/src/main/js/components/controls/IdentityProviderLink.css b/server/sonar-web/src/main/js/components/controls/IdentityProviderLink.css deleted file mode 100644 index 8230ef0473b..00000000000 --- a/server/sonar-web/src/main/js/components/controls/IdentityProviderLink.css +++ /dev/null @@ -1,64 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -a.identity-provider-link { - display: block; - line-height: 22px; - padding: var(--gridSize) calc(1.5 * var(--gridSize)); - border: 1px solid rgba(0, 0, 0, 0.15); - border-radius: 2px; - box-sizing: border-box; - background-color: var(--darkBlue); - color: #fff; - white-space: nowrap; -} - -a.identity-provider-link.small { - line-height: 14px; - padding: calc(var(--gridSize) / 2) var(--gridSize); -} - -a.identity-provider-link:hover, -a.identity-provider-link:focus { - box-shadow: inset 0 0 0 100px rgba(255, 255, 255, 0.1); -} - -a.identity-provider-link.dark-text { - color: var(--secondFontColor); -} - -a.identity-provider-link.dark-text:hover, -a.identity-provider-link.dark-text:focus { - box-shadow: inset 0 0 0 100px rgba(0, 0, 0, 0.1); -} - -a.identity-provider-link > img { - padding-right: calc(1.5 * var(--gridSize)); -} - -a.identity-provider-link.small > img { - padding-right: var(--gridSize); -} - -a.identity-provider-link > span::before { - content: ''; - opacity: 0.4; - border-left: 1px var(--gray71) solid; - margin-right: calc(1.5 * var(--gridSize)); -} diff --git a/server/sonar-web/src/main/js/components/controls/IdentityProviderLink.tsx b/server/sonar-web/src/main/js/components/controls/IdentityProviderLink.tsx deleted file mode 100644 index 1757e49876a..00000000000 --- a/server/sonar-web/src/main/js/components/controls/IdentityProviderLink.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import { isDarkColor } from 'design-system'; -import * as React from 'react'; -import { getBaseUrl } from '../../helpers/system'; -import './IdentityProviderLink.css'; - -interface Props { - backgroundColor: string; - children: React.ReactNode; - className?: string; - iconPath: string; - name: string; - onClick?: () => void; - small?: boolean; - url: string | undefined; -} - -export default function IdentityProviderLink({ - backgroundColor, - children, - className, - iconPath, - name, - onClick, - small, - url, -}: Props) { - const size = small ? 14 : 20; - - return ( - <a - className={classNames( - 'identity-provider-link', - { 'dark-text': !isDarkColor(backgroundColor), small }, - className, - )} - href={url} - onClick={onClick} - style={{ backgroundColor }} - > - <img alt={name} height={size} src={getBaseUrl() + iconPath} width={size} /> - {children} - </a> - ); -} diff --git a/server/sonar-web/src/main/js/components/controls/ListFooter.tsx b/server/sonar-web/src/main/js/components/controls/ListFooter.tsx index ce03c60eb7a..70071fb047d 100644 --- a/server/sonar-web/src/main/js/components/controls/ListFooter.tsx +++ b/server/sonar-web/src/main/js/components/controls/ListFooter.tsx @@ -24,8 +24,6 @@ import * as React from 'react'; import { translate, translateWithParameters } from '../../helpers/l10n'; import { formatMeasure } from '../../helpers/measures'; import { MetricType } from '../../types/metrics'; -import LegacySpinner from '../ui/Spinner'; -import { Button } from './buttons'; export interface ListFooterProps { loadMoreAriaLabel?: string; @@ -38,7 +36,6 @@ export interface ListFooterProps { reload?: () => void; ready?: boolean; total?: number; - useMIUIButtons?: boolean; } export default function ListFooter(props: ListFooterProps) { @@ -52,7 +49,6 @@ export default function ListFooter(props: ListFooterProps) { total, pageSize, ready = true, - useMIUIButtons = false, } = props; const rootNode = React.useRef<HTMLDivElement>(null); @@ -76,27 +72,27 @@ export default function ListFooter(props: ListFooterProps) { let button; if (needReload && props.reload) { - button = React.createElement( - useMIUIButtons ? ButtonSecondary : Button, - { - 'data-test': 'reload', - className: classNames('sw-ml-2', { 'sw-body-sm': useMIUIButtons }), - disabled: loading, - onClick: props.reload, - } as Button['props'], - translate('reload'), + button = ( + <ButtonSecondary + data-test="reload" + className="sw-ml-2 sw-body-sm" + disabled={loading} + onClick={props.reload} + > + {translate('reload')} + </ButtonSecondary> ); } else if (hasMore && props.loadMore) { - button = React.createElement( - useMIUIButtons ? ButtonSecondary : Button, - { - 'aria-label': loadMoreAriaLabel, - 'data-test': 'show-more', - className: classNames('sw-ml-2', { 'sw-body-sm': useMIUIButtons }), - disabled: loading, - onClick: onLoadMore, - } as Button['props'], - translate('show_more'), + button = ( + <ButtonSecondary + aria-label={loadMoreAriaLabel} + data-test="show-more" + className="sw-ml-2 sw-body-sm" + disabled={loading} + onClick={onLoadMore} + > + {translate('show_more')} + </ButtonSecondary> ); } @@ -121,12 +117,7 @@ export default function ListFooter(props: ListFooterProps) { : translateWithParameters('x_show', formatMeasure(count, MetricType.Integer))} </span> {button} - {/* eslint-disable local-rules/no-conditional-rendering-of-deferredspinner */} - {useMIUIButtons ? ( - <Spinner loading={loading} className="sw-ml-2" /> - ) : ( - <LegacySpinner loading={loading} className="sw-ml-2" /> - )} + <Spinner loading={loading} className="sw-ml-2" /> </StyledDiv> ); } diff --git a/server/sonar-web/src/main/js/components/controls/Modal.css b/server/sonar-web/src/main/js/components/controls/Modal.css deleted file mode 100644 index e193ffbef95..00000000000 --- a/server/sonar-web/src/main/js/components/controls/Modal.css +++ /dev/null @@ -1,228 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.modal, -.ReactModal__Content { - position: fixed; - z-index: var(--modalZIndex); - top: 0; - left: 50%; - margin-left: -270px; - width: 540px; - background-color: #fff; - opacity: 0; - transition: all 0.2s ease; - border-radius: 3px; -} - -.modal:focus, -.ReactModal__Content:focus { - outline: none; -} - -.modal.in, -.ReactModal__Content--after-open { - top: 15%; - opacity: 1; -} - -.modal-small { - width: 450px; - margin-left: -225px; -} - -.modal-medium { - width: 830px; - margin-left: -415px; -} - -.modal-large { - width: calc(100% - 40px); - max-width: 1280px; - min-width: 1040px; - margin-left: 0; - transform: translateX(-50%); -} - -.modal-overlay, -.ReactModal__Overlay { - position: fixed; - z-index: var(--modalOverlayZIndex); - top: 0; - bottom: 0; - left: 0; - right: 0; - background-color: rgba(0, 0, 0, 0.7); - opacity: 0; - transition: all 0.2s ease; -} - -.modal-overlay.in, -.ReactModal__Overlay--after-open { - opacity: 1; -} - -.modal-no-backdrop { - background-color: transparent; -} - -.modal-open, -.ReactModal__Body--open { - overflow: hidden; - margin-right: var(--sbw); -} - -.modal-head { - padding: calc(4 * var(--gridSize)); - padding-bottom: 0; -} - -.modal-head h1, -.modal-head h2 { - margin: 0; - font-size: var(--bigFontSize); - font-weight: bold; - line-height: normal; - overflow-wrap: break-word; -} - -.modal-body { - padding: var(--pagePadding) calc(4 * var(--gridSize)); -} - -.modal-container { - max-height: 60vh; - box-sizing: border-box; - overflow-y: auto; - border-top: 1px solid var(--barBorderColor); - margin-top: var(--pagePadding); - padding-right: calc(4 * var(--gridSize)); -} - -.modal-container > :last-child { - margin-bottom: var(--pagePadding); -} - -.modal-field, -.modal-validation-field { - clear: both; - display: block; - padding: 0; - margin-bottom: calc(var(--gridSize) * 2); -} - -.modal-field label, -.modal-validation-field label, -.modal-field legend { - display: block; - font-weight: bold; - padding-bottom: calc(var(--gridSize) / 2); -} - -.modal-field a.icon-checkbox, -.modal-field input, -.modal-field select, -.modal-field textarea, -.modal-field .Select { - margin-right: 5px; -} - -.modal-field a.icon-checkbox { - height: 24px; -} - -.modal-field input[type='radio'], -.modal-field input[type='checkbox'] { - margin-top: 5px; - margin-bottom: 4px; -} - -.modal-field > .icon-checkbox { - padding-top: 6px; - padding-right: 8px; -} - -.modal-field input[type='text'], -.modal-field input[type='email'], -.modal-field input[type='password'], -.modal-field textarea, -.modal-field select, -.modal-field .Select { - width: 100%; -} - -.modal-validation-field input, -.modal-validation-field textarea, -.modal-validation-field .Select { - margin-right: var(--gridSize); - margin-bottom: 2px; - width: calc(100% - 3 * var(--gridSize)); -} - -.modal-field textarea, -.modal-validation-field textarea { - max-width: 100%; - min-width: 100%; - max-height: 50vh; - min-height: var(--controlHeight); -} -.modal-validation-field input:not(.is-invalid), -.modal-validation-field .Select:not(.is-invalid) { - margin-bottom: calc(var(--tinyControlHeight) + 2px); -} - -.modal-field-description { - line-height: 1.4; - color: var(--secondFontColor); - font-size: var(--smallFontSize); - overflow: hidden; - text-overflow: ellipsis; - margin-top: 2px; -} - -.modal-field input[type='text'].invalid { - border-color: var(--red); -} - -.modal-foot { - padding: var(--pagePadding) calc(4 * var(--gridSize)); - border-top: 1px solid var(--barBorderColor); - background-color: var(--barBackgroundColor); - border-radius: 3px; - text-align: right; -} - -.modal-foot button, -.modal-foot .button, -.modal-foot input[type='submit'], -.modal-foot input[type='button'] { - margin-left: var(--gridSize); -} - -.modal-foot button:first-of-type, -.modal-foot .button:first-of-type, -.modal-foot input[type='submit']:first-of-type, -.modal-foot input[type='button']:first-of-type { - margin-left: 0; -} - -.modal-foot-clear { - border-top: 0; - background-color: transparent; -} diff --git a/server/sonar-web/src/main/js/components/controls/Modal.tsx b/server/sonar-web/src/main/js/components/controls/Modal.tsx deleted file mode 100644 index f3c391f5cd6..00000000000 --- a/server/sonar-web/src/main/js/components/controls/Modal.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import * as React from 'react'; -import ReactModal from 'react-modal'; -import { getReactDomContainerSelector } from '../../helpers/system'; -import './Modal.css'; - -ReactModal.setAppElement(getReactDomContainerSelector()); - -export interface ModalProps { - children: React.ReactNode; - size?: 'small' | 'medium' | 'large'; - noBackdrop?: boolean; -} - -interface Props extends ModalProps { - /* String or object className to be applied to the modal content. */ - className?: string; - - /* String or object className to be applied to the overlay. */ - overlayClassName?: string; - - /* Function that will be run after the modal has opened. */ - onAfterOpen?(): void; - - /* Function that will be run after the modal has closed. */ - onAfterClose?(): void; - - /* Function that will be run when the modal is requested to be closed, prior to actually closing. */ - onRequestClose?(event: React.MouseEvent | React.KeyboardEvent): void; - - /* Boolean indicating if the modal should be focused after render */ - shouldFocusAfterRender?: boolean; - - /* Boolean indicating if the overlay should close the modal. Defaults to true. */ - shouldCloseOnOverlayClick?: boolean; - - /* Boolean indicating if pressing the esc key should close the modal */ - shouldCloseOnEsc?: boolean; - - /* String indicating how the content container should be announced to screenreaders. */ - contentLabel?: string; -} - -export default function Modal(props: Props) { - return ( - <ReactModal - className={classNames('modal', { - 'modal-small': props.size === 'small', - 'modal-medium': props.size === 'medium', - 'modal-large': props.size === 'large', - })} - isOpen - overlayClassName={classNames('modal-overlay', { 'modal-no-backdrop': props.noBackdrop })} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/components/controls/Radio.css b/server/sonar-web/src/main/js/components/controls/Radio.css deleted file mode 100644 index 19c19819d5c..00000000000 --- a/server/sonar-web/src/main/js/components/controls/Radio.css +++ /dev/null @@ -1,76 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.icon-radio { - position: relative; - display: inline-block; - vertical-align: top; - width: 14px; - height: 14px; - margin: 1px; - border: 1px solid var(--gray60); - border-radius: 12px; - box-sizing: border-box; - transition: border-color 0.3s ease; - flex-shrink: 0; -} - -.icon-radio:after { - position: absolute; - top: 2px; - left: 2px; - display: block; - width: 8px; - height: 8px; - border-radius: 8px; - background-color: var(--darkBlue); - content: ''; - opacity: 0; - transition: opacity 0.3s ease; -} - -.link-radio .icon-radio.is-checked:after { - opacity: 1; -} - -.link-radio { - color: inherit; - border-bottom: none; -} - -.link-radio:not(.disabled):hover, -.link-radio:not(.disabled):active, -.link-radio:not(.disabled):focus { - color: inherit; -} - -.link-radio:not(.disabled):hover > .icon-radio { - border-color: var(--blue); -} - -.link-radio.disabled, -.link-radio.disabled:hover, -.link-radio.disabled label { - color: var(--disableGrayText); - cursor: not-allowed; -} - -.link-radio.disabled .icon-radio:after { - background-color: var(--disableGrayBg); -} diff --git a/server/sonar-web/src/main/js/components/controls/Radio.tsx b/server/sonar-web/src/main/js/components/controls/Radio.tsx deleted file mode 100644 index 0d6a505987e..00000000000 --- a/server/sonar-web/src/main/js/components/controls/Radio.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import * as React from 'react'; -import './Radio.css'; - -interface Props { - checked: boolean; - className?: string; - alignLabel?: boolean; - disabled?: boolean; - onCheck: (value: string) => void; - value: string; - ariaLabel?: string; -} - -export default class Radio extends React.PureComponent<React.PropsWithChildren<Props>> { - handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => { - event.preventDefault(); - - if (!this.props.disabled) { - this.props.onCheck(this.props.value); - } - }; - - render() { - const { className, checked, children, disabled, alignLabel = false, ariaLabel } = this.props; - - return ( - <a - aria-checked={checked} - className={classNames( - alignLabel ? 'display-inline-flex-start' : 'display-inline-flex-center', - 'link-radio', - className, - { disabled }, - )} - href="#" - onClick={this.handleClick} - role="radio" - aria-label={ariaLabel} - > - <i className={classNames('icon-radio', 'spacer-right', { 'is-checked': checked })} /> - {children} - </a> - ); - } -} diff --git a/server/sonar-web/src/main/js/components/controls/RadioCard.css b/server/sonar-web/src/main/js/components/controls/RadioCard.css deleted file mode 100644 index 8125e7cc17a..00000000000 --- a/server/sonar-web/src/main/js/components/controls/RadioCard.css +++ /dev/null @@ -1,130 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.radio-card { - display: flex; - flex-direction: column; - width: 450px; - min-height: 210px; - background-color: #fff; - border: solid 1px var(--barBorderColor); - border-radius: 3px; - box-sizing: border-box; - margin-right: calc(2 * var(--gridSize)); - transition: all 0.2s ease; -} - -.radio-card.animated { - height: 0; - border-width: 0; - overflow: hidden; -} - -.radio-card.animated.open { - height: 210px; - border-width: 1px; -} - -.radio-card.highlight { - box-shadow: var(--defaultShadow); -} - -.radio-card:last-child { - margin-right: 0; -} - -.radio-card-vertical { - width: 100%; - min-height: auto; -} - -.radio-card-actionable { - cursor: pointer; -} - -.radio-card-actionable:not(.disabled):hover { - box-shadow: var(--defaultShadow); - transform: translateY(-2px); -} - -.radio-card-actionable.selected { - border-color: var(--darkBlue); -} - -/* - * Disabled transform property because it moves the element to a new stacking context - * creating z-index conflicts with other components. - * This is a problem with a vertical list of RadioCards where a select might be above another RadioCard - */ -.radio-card-actionable.radio-card-vertical:not(.disabled):hover { - box-shadow: none; - transform: none; -} - -.radio-card-actionable.radio-card-vertical:not(.selected):not(.disabled):hover { - border-color: var(--lightBlue); -} - -.radio-card-actionable.selected .radio-card-recommended { - border: solid 1px var(--darkBlue); - border-top: none; -} - -.radio-card-actionable.disabled { - cursor: not-allowed; - background-color: var(--disableGrayBg); - border-color: var(--disableGrayBorder); -} - -.radio-card-actionable.disabled h2, -.radio-card-actionable.disabled ul { - color: var(--disableGrayText); -} - -.radio-card-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: calc(2 * var(--gridSize)) calc(2 * var(--gridSize)) 0; -} - -.radio-card-body { - flex-grow: 1; - display: flex; - flex-direction: column; - justify-content: space-between; - padding: 0 calc(2 * var(--gridSize)) calc(2 * var(--gridSize)); -} - -.radio-card-body .alert { - margin-bottom: 0; -} - -.radio-card-recommended { - position: relative; - padding: 6px calc(var(--gridSize) * 2); - left: -1px; - bottom: -1px; - width: 450px; - color: #fff; - background-color: var(--blue); - border-radius: 0 0 3px 3px; - box-sizing: border-box; - font-size: var(--smallFontSize); -} diff --git a/server/sonar-web/src/main/js/components/controls/RadioCard.tsx b/server/sonar-web/src/main/js/components/controls/RadioCard.tsx deleted file mode 100644 index 605735ec9c0..00000000000 --- a/server/sonar-web/src/main/js/components/controls/RadioCard.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import * as React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { KeyboardKeys } from '../../helpers/keycodes'; -import { translate } from '../../helpers/l10n'; -import RecommendedIcon from '../icons/RecommendedIcon'; -import './Radio.css'; -import './RadioCard.css'; - -export interface RadioCardProps { - className?: string; - disabled?: boolean; - onClick?: () => void; - selected?: boolean; - noRadio?: boolean; -} - -interface Props extends RadioCardProps { - children: React.ReactNode; - recommended?: string; - title: React.ReactNode; - titleInfo?: React.ReactNode; - vertical?: boolean; - label?: string; -} - -export default function RadioCard(props: Props) { - const { - className, - disabled, - onClick, - recommended, - selected, - titleInfo, - label, - vertical = false, - noRadio = false, - } = props; - const isActionable = Boolean(onClick); - const clickHandler = isActionable && !disabled && !selected ? onClick : undefined; - - const keyPressHandler = (event: React.KeyboardEvent<HTMLDivElement>) => { - if (event.code === KeyboardKeys.Enter) { - clickHandler?.(); - } - }; - - return ( - <div - aria-checked={selected} - className={classNames( - 'radio-card', - { - 'radio-card-actionable': isActionable, - 'radio-card-vertical': vertical, - disabled, - selected, - }, - className, - )} - onClick={clickHandler} - onKeyPress={keyPressHandler} - role="radio" - aria-label={label} - aria-disabled={disabled} - tabIndex={disabled ? -1 : 0} - > - <h2 className="radio-card-header big-spacer-bottom"> - <span className="display-flex-center link-radio"> - {isActionable && !noRadio && ( - <i className={classNames('icon-radio', 'spacer-right', { 'is-checked': selected })} /> - )} - {props.title} - </span> - {titleInfo} - </h2> - <div className="radio-card-body">{props.children}</div> - {recommended && ( - <div className="radio-card-recommended"> - <RecommendedIcon className="spacer-right" /> - <FormattedMessage - defaultMessage={recommended} - id={recommended} - values={{ recommended: <strong>{translate('recommended')}</strong> }} - /> - </div> - )} - </div> - ); -} diff --git a/server/sonar-web/src/main/js/components/controls/SearchBox.css b/server/sonar-web/src/main/js/components/controls/SearchBox.css deleted file mode 100644 index c00548245cd..00000000000 --- a/server/sonar-web/src/main/js/components/controls/SearchBox.css +++ /dev/null @@ -1,110 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.search-box { - position: relative; - display: inline-block; - vertical-align: middle; - font-size: 0; - white-space: nowrap; -} - -.search-box, -.search-box-input { - width: 100%; - max-width: 300px; -} - -.search-box-input { - /* for magnifier icon */ - padding-left: var(--controlHeight) !important; - /* for clear button */ - padding-right: var(--controlHeight) !important; - font-size: var(--baseFontSize); -} - -.search-box-input::-webkit-search-decoration, -.search-box-input::-webkit-search-cancel-button, -.search-box-input::-webkit-search-results-button, -.search-box-input::-webkit-search-results-decoration { - -webkit-appearance: none; - display: none; -} - -.search-box-input::-ms-clear, -.search-box-input::-ms-reveal { - display: none; - width: 0; - height: 0; -} - -.search-box-note { - position: absolute; - top: 1px; - left: 40px; - right: var(--controlHeight); - line-height: var(--controlHeight); - color: var(--secondFontColor); - font-size: var(--smallFontSize); - text-align: right; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - pointer-events: none; -} - -.search-box-input:focus ~ .search-box-magnifier { - color: var(--darkBlue); -} - -.search-box-magnifier { - position: absolute; - top: 4px; - left: 4px; - color: var(--gray52); - transition: color 0.3s ease; -} - -.search-box > .spinner { - position: absolute; - top: 4px; - left: 5px; -} - -.search-box-clear { - position: absolute; - top: 4px; - right: 4px; -} - -.search-box-clear.button:focus { - box-shadow: - 0 0 0 1px white, - 0 0 0 4px rgba(35, 106, 151, 0.5); -} - -.search-box-input-note { - position: absolute; - top: 100%; - left: 0; - line-height: 1; - color: var(--secondFontColor); - font-size: var(--smallFontSize); - white-space: nowrap; -} diff --git a/server/sonar-web/src/main/js/components/controls/SearchBox.tsx b/server/sonar-web/src/main/js/components/controls/SearchBox.tsx deleted file mode 100644 index d5ce452ffa4..00000000000 --- a/server/sonar-web/src/main/js/components/controls/SearchBox.tsx +++ /dev/null @@ -1,182 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import { debounce, DebouncedFunc } from 'lodash'; -import * as React from 'react'; -import { KeyboardKeys } from '../../helpers/keycodes'; -import { translate, translateWithParameters } from '../../helpers/l10n'; -import SearchIcon from '../icons/SearchIcon'; -import Spinner from '../ui/Spinner'; -import { ClearButton } from './buttons'; -import './SearchBox.css'; - -interface Props { - autoFocus?: boolean; - className?: string; - id?: string; - innerRef?: (node: HTMLInputElement | null) => void; - loading?: boolean; - maxLength?: number; - minLength?: number; - onChange: (value: string) => void; - onClick?: React.MouseEventHandler<HTMLInputElement>; - onFocus?: React.FocusEventHandler<HTMLInputElement>; - onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>; - placeholder: string; - value?: string; -} - -interface State { - value: string; -} - -const DEFAULT_MAX_LENGTH = 100; -const DEBOUNCE_DELAY = 250; - -export default class SearchBox extends React.PureComponent<Props, State> { - debouncedOnChange: DebouncedFunc<(query: string) => void>; - input?: HTMLInputElement | null; - - constructor(props: Props) { - super(props); - this.state = { value: props.value ?? '' }; - this.debouncedOnChange = debounce(this.props.onChange, DEBOUNCE_DELAY); - } - - componentDidUpdate(prevProps: Props) { - if ( - // input is controlled - this.props.value !== undefined && - // parent is aware of last change - // can happen when previous value was less than min length - this.state.value === prevProps.value && - this.state.value !== this.props.value - ) { - this.setState({ value: this.props.value }); - } - } - - changeValue = (value: string, debounced = true) => { - const { minLength } = this.props; - if (value.length === 0) { - // immediately notify when value is empty - this.props.onChange(''); - // and cancel scheduled callback - this.debouncedOnChange.cancel(); - } else if (!minLength || minLength <= value.length) { - if (debounced) { - this.debouncedOnChange(value); - } else { - this.props.onChange(value); - } - } - }; - - handleInputChange = (event: React.SyntheticEvent<HTMLInputElement>) => { - const { value } = event.currentTarget; - this.setState({ value }); - this.changeValue(value); - }; - - handleInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => { - if (event.nativeEvent.key === KeyboardKeys.Escape) { - event.preventDefault(); - this.handleResetClick(); - } - if (this.props.onKeyDown) { - this.props.onKeyDown(event); - } - }; - - handleResetClick = () => { - this.changeValue('', false); - if (this.props.value === undefined || this.props.value === '') { - this.setState({ value: '' }); - } - if (this.input) { - this.input.focus(); - } - }; - - ref = (node: HTMLInputElement | null) => { - this.input = node; - if (this.props.innerRef) { - this.props.innerRef(node); - } - }; - - render() { - const { loading, minLength, maxLength = DEFAULT_MAX_LENGTH } = this.props; - const { value } = this.state; - - const inputClassName = classNames('search-box-input', { - touched: value.length > 0 && (!minLength || minLength > value.length), - }); - - const tooShort = minLength !== undefined && value.length > 0 && value.length < minLength; - - return ( - <div - className={classNames('search-box', this.props.className)} - id={this.props.id} - title={ - tooShort && minLength !== undefined - ? translateWithParameters('select2.tooShort', minLength) - : '' - } - > - <input - aria-label={this.props.placeholder} - autoComplete="off" - autoFocus={this.props.autoFocus} - className={inputClassName} - maxLength={maxLength} - onChange={this.handleInputChange} - onClick={this.props.onClick} - onFocus={this.props.onFocus} - onKeyDown={this.handleInputKeyDown} - placeholder={this.props.placeholder} - ref={this.ref} - type="search" - value={value} - /> - - <Spinner loading={loading !== undefined ? loading : false}> - <SearchIcon className="search-box-magnifier" /> - </Spinner> - - {value && ( - <ClearButton - aria-label={translate('clear')} - className="button-tiny search-box-clear" - iconProps={{ size: 12 }} - onClick={this.handleResetClick} - /> - )} - - <span aria-live="polite" className="search-box-note"> - {tooShort && - minLength !== undefined && - translateWithParameters('select2.tooShort', minLength)} - </span> - </div> - ); - } -} diff --git a/server/sonar-web/src/main/js/components/controls/Select.tsx b/server/sonar-web/src/main/js/components/controls/Select.tsx deleted file mode 100644 index ab222c15191..00000000000 --- a/server/sonar-web/src/main/js/components/controls/Select.tsx +++ /dev/null @@ -1,369 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import styled from '@emotion/styled'; -import classNames from 'classnames'; -import { omit } from 'lodash'; -import * as React from 'react'; -import ReactSelect, { - ClearIndicatorProps, - components, - DropdownIndicatorProps, - GroupBase, - LoadingIndicatorProps, - MultiValueRemoveProps, - Props as NamedProps, - StylesConfig, -} from 'react-select'; -import AsyncReactSelect, { AsyncProps } from 'react-select/async'; -import AsyncCreatableReactSelect, { AsyncCreatableProps } from 'react-select/async-creatable'; -import { colors, others, sizes, zIndexes } from '../../app/theme'; -import { ClearButton } from './buttons'; - -const ArrowSpan = styled.span` - border-color: ${colors.gray52} transparent transparent; - border-style: solid; - border-width: 4px 4px 2px; - display: inline-block; - height: 0; - width: 0; -`; - -export interface LabelValueSelectOption<V = string> { - label: string; - value: V; -} - -interface StyleExtensionProps { - large?: boolean; -} - -export function dropdownIndicator< - Option = LabelValueSelectOption, - IsMulti extends boolean = boolean, - Group extends GroupBase<Option> = GroupBase<Option>, ->({ innerProps }: DropdownIndicatorProps<Option, IsMulti, Group>) { - return <ArrowSpan {...innerProps} />; -} - -export function clearIndicator< - Option = LabelValueSelectOption, - IsMulti extends boolean = boolean, - Group extends GroupBase<Option> = GroupBase<Option>, ->(props: ClearIndicatorProps<Option, IsMulti, Group>) { - const { innerProps } = props; - return ( - <div {...innerProps} className="spacer-left spacer-right"> - {/* We use tabindex="-1" to prevent the clear button from being focused via tabbing.*/} - {/* This is done to align with react-select default behavior and because backspace already clears the select value. */} - <ClearButton className="button-tiny" iconProps={{ size: 12 }} tabIndex={-1} /> - </div> - ); -} - -export function loadingIndicator< - Option = LabelValueSelectOption, - IsMulti extends boolean = boolean, - Group extends GroupBase<Option> = GroupBase<Option>, ->({ innerProps }: LoadingIndicatorProps<Option, IsMulti, Group>) { - return <i className={classNames('spinner spacer-left spacer-right', innerProps.className)} />; -} - -export function multiValueRemove< - Option = LabelValueSelectOption, - IsMulti extends boolean = boolean, - Group extends GroupBase<Option> = GroupBase<Option>, ->(props: MultiValueRemoveProps<Option, IsMulti, Group>) { - return <components.MultiValueRemove {...props}>×</components.MultiValueRemove>; -} - -export default function Select< - Option = LabelValueSelectOption, - IsMulti extends boolean = boolean, - Group extends GroupBase<Option> = GroupBase<Option>, ->(props: NamedProps<Option, IsMulti, Group> & Readonly<StyleExtensionProps>) { - return ( - <ReactSelect<Option, IsMulti, Group> - {...omit(props, 'className', 'large')} - styles={selectStyle<Option, IsMulti, Group>(props)} - className={classNames('react-select', props.className)} - classNamePrefix="react-select" - components={{ - ...props.components, - DropdownIndicator: dropdownIndicator, - ClearIndicator: clearIndicator, - MultiValueRemove: multiValueRemove, - }} - /> - ); -} - -export function CreatableSelect< - Option = LabelValueSelectOption, - IsMulti extends boolean = boolean, - Group extends GroupBase<Option> = GroupBase<Option>, ->(props: AsyncCreatableProps<Option, IsMulti, Group>) { - return ( - <AsyncCreatableReactSelect<Option, IsMulti, Group> - {...props} - styles={selectStyle<Option, IsMulti, Group>()} - components={{ - ...props.components, - DropdownIndicator: dropdownIndicator, - ClearIndicator: clearIndicator, - MultiValueRemove: multiValueRemove, - LoadingIndicator: loadingIndicator, - }} - /> - ); -} - -export function SearchSelect< - Option = LabelValueSelectOption, - IsMulti extends boolean = boolean, - Group extends GroupBase<Option> = GroupBase<Option>, ->( - props: NamedProps<Option, IsMulti, Group> & - AsyncProps<Option, IsMulti, Group> & - Readonly<StyleExtensionProps>, -) { - return ( - <AsyncReactSelect<Option, IsMulti, Group> - {...omit(props, 'className', 'large')} - styles={selectStyle<Option, IsMulti, Group>(props)} - className={classNames('react-select', props.className)} - classNamePrefix="react-select" - components={{ - ...props.components, - DropdownIndicator: dropdownIndicator, - ClearIndicator: clearIndicator, - MultiValueRemove: multiValueRemove, - LoadingIndicator: loadingIndicator, - }} - /> - ); -} - -export function selectStyle< - Option = LabelValueSelectOption, - IsMulti extends boolean = boolean, - Group extends GroupBase<Option> = GroupBase<Option>, ->( - props?: NamedProps<Option, IsMulti, Group> & - AsyncProps<Option, IsMulti, Group> & - Readonly<StyleExtensionProps>, -): StylesConfig<Option, IsMulti, Group> { - return { - container: () => ({ - position: 'relative', - display: 'inline-block', - verticalAlign: 'middle', - fontSize: '12px', - textAlign: 'left', - width: '100%', - }), - control: (_provided, state) => ({ - position: 'relative', - display: 'flex', - width: '100%', - minHeight: `${sizes.controlHeight}`, - lineHeight: `calc(${sizes.controlHeight} - 2px)`, - border: `1px solid ${state.isFocused ? colors.blue : colors.gray80}`, - borderCollapse: 'separate', - borderRadius: '2px', - backgroundColor: state.isDisabled ? colors.disableGrayBg : '#fff', - boxSizing: 'border-box', - color: `${colors.baseFontColor}`, - cursor: 'default', - outline: 'none', - padding: props?.large ? '4px 0px' : '0', - }), - singleValue: () => ({ - bottom: 0, - left: 0, - lineHeight: '23px', - padding: props?.large ? '4px 8px' : '0 8px', - paddingLeft: '8px', - paddingRight: '24px', - position: 'absolute', - right: 0, - top: 0, - maxWidth: '100%', - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - }), - valueContainer: (_provided, state) => { - if (state.hasValue && state.isMulti) { - return { - lineHeight: '23px', - paddingLeft: '1px', - }; - } - - return { - bottom: 0, - left: 0, - lineHeight: '23px', - paddingLeft: '8px', - paddingRight: '24px', - position: 'absolute', - right: 0, - top: 0, - maxWidth: '100%', - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - display: 'flex', - }; - }, - indicatorsContainer: (_provided, state) => ({ - position: 'relative', - cursor: state.isDisabled ? 'default' : 'pointer', - textAlign: 'end', - verticalAlign: 'middle', - width: '20px', - paddingRight: '5px', - flex: 1, - display: 'flex', - justifyContent: 'end', - alignItems: 'center', - }), - indicatorSeparator: () => ({ - display: 'none', - }), - multiValue: () => ({ - display: 'inline-block', - backgroundColor: 'rgba(0, 126, 255, 0.08)', - borderRadius: '2px', - border: '1px solid rgba(0, 126, 255, 0.24)', - color: '#333', - maxWidth: '200px', - fontSize: '12px', - lineHeight: '14px', - margin: '1px 4px 1px 1px', - verticalAlign: 'top', - }), - multiValueLabel: () => ({ - display: 'inline-block', - cursor: 'default', - padding: '2px 5px', - overflow: 'hidden', - marginRight: 'auto', - maxWidth: 'calc(200px - 28px)', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - verticalAlign: 'middle', - }), - multiValueRemove: () => ({ - order: '-1', - cursor: 'pointer', - borderLeft: '1px solid rgba(0, 126, 255, 0.24)', - verticalAlign: 'middle', - padding: '1px 5px', - fontSize: '12px', - lineHeight: '14px', - display: 'inline-block', - }), - menu: () => ({ - borderBottomRightRadius: '4px', - borderBottomLeftRadius: '4px', - backgroundColor: colors.white, - border: `1px solid ${colors.neutral200}`, - borderTopColor: `${colors.barBorderColor}`, - boxSizing: 'border-box', - marginTop: '-1px', - maxHeight: '200px', - zIndex: `${zIndexes.dropdownMenuZIndex}`, - webkitOverflowScrolling: 'touch', - boxShadow: `${others.defaultShadow}`, - position: 'absolute', - top: '100%', - minWidth: '100%', - }), - menuPortal: (baseStyles) => ({ - ...baseStyles, - borderBottomRightRadius: '4px', - borderBottomLeftRadius: '4px', - marginTop: '-1px', - backgroundColor: colors.white, - border: `1px solid ${colors.neutral200}`, - borderTopColor: `${colors.barBorderColor}`, - boxSizing: 'border-box', - maxHeight: '200px', - zIndex: `${zIndexes.dropdownMenuZIndex}`, - webkitOverflowScrolling: 'touch', - boxShadow: `${others.defaultShadow}`, - }), - menuList: () => ({ - boxSizing: 'border-box', - maxHeight: '198px', - padding: '5px 0', - overflowY: 'auto', - }), - placeholder: () => ({ - position: 'absolute', - color: '#666', - }), - option: (_provided, state) => { - let borderLeftColor = 'transparent'; - let backgroundColor = colors.white; - - if (state.isFocused && state.isSelected) { - borderLeftColor = colors.info500; - backgroundColor = colors.info100; - } else if (state.isFocused) { - borderLeftColor = colors.blacka60; - backgroundColor = colors.neutral50; - } else if (state.isSelected) { - borderLeftColor = colors.info500; - backgroundColor = colors.info50; - } - - return { - display: 'block', - lineHeight: '20px', - padding: props?.large ? '4px 8px' : '0 8px', - boxSizing: 'border-box', - color: state.isDisabled ? colors.disableGrayText : colors.neutral800, - backgroundColor, - borderLeft: '2px solid transparent', - borderLeftColor, - fontSize: `${sizes.smallFontSize}`, - cursor: state.isDisabled ? 'default' : 'pointer', - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - }; - }, - input: () => ({ - display: 'flex', - alignItems: 'center', - }), - loadingIndicator: () => ({ - position: 'absolute', - padding: '8px', - fontSize: '4px', - }), - noOptionsMessage: () => ({ - color: `${colors.gray60}`, - padding: '8px 10px', - }), - }; -} diff --git a/server/sonar-web/src/main/js/components/controls/SelectList.tsx b/server/sonar-web/src/main/js/components/controls/SelectList.tsx index d68ad1c9f3e..2ae7e857c6f 100644 --- a/server/sonar-web/src/main/js/components/controls/SelectList.tsx +++ b/server/sonar-web/src/main/js/components/controls/SelectList.tsx @@ -195,7 +195,6 @@ export default class SelectList extends React.PureComponent<Props, State> { needReload={this.props.needToReload} reload={this.onReload} total={this.props.elementsTotalCount} - useMIUIButtons /> )} </PageContentFontWrapper> diff --git a/server/sonar-web/src/main/js/components/controls/SimpleModal.tsx b/server/sonar-web/src/main/js/components/controls/SimpleModal.tsx deleted file mode 100644 index 1d3a408b2bc..00000000000 --- a/server/sonar-web/src/main/js/components/controls/SimpleModal.tsx +++ /dev/null @@ -1,101 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import Modal, { ModalProps } from './Modal'; - -export interface ChildrenProps { - onCloseClick: (event?: React.SyntheticEvent<HTMLElement>) => void; - onFormSubmit: (event: React.SyntheticEvent<HTMLFormElement>) => void; - onSubmitClick: (event?: React.SyntheticEvent<HTMLElement>) => void; - submitting: boolean; -} - -interface Props extends Omit<ModalProps, 'children'> { - children: (props: ChildrenProps) => React.ReactNode; - header: string; - onClose: () => void; - onSubmit: () => void | Promise<void | Response>; -} - -interface State { - submitting: boolean; -} - -export default class SimpleModal extends React.Component<Props, State> { - mounted = false; - state: State = { submitting: false }; - - componentDidMount() { - this.mounted = true; - } - - componentWillUnmount() { - this.mounted = false; - } - - stopSubmitting = () => { - if (this.mounted) { - this.setState({ submitting: false }); - } - }; - - handleCloseClick = (event?: React.SyntheticEvent<HTMLElement>) => { - if (event) { - event.preventDefault(); - event.currentTarget.blur(); - } - this.props.onClose(); - }; - - handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => { - event.preventDefault(); - this.submit(); - }; - - handleSubmitClick = (event?: React.SyntheticEvent<HTMLElement>) => { - if (event) { - event.preventDefault(); - event.currentTarget.blur(); - } - this.submit(); - }; - - submit = () => { - const result = this.props.onSubmit(); - if (result) { - this.setState({ submitting: true }); - result.then(this.stopSubmitting, this.stopSubmitting); - } - }; - - render() { - const { children, header, onClose, onSubmit, ...modalProps } = this.props; - return ( - <Modal contentLabel={header} onRequestClose={onClose} {...modalProps}> - {children({ - onCloseClick: this.handleCloseClick, - onFormSubmit: this.handleFormSubmit, - onSubmitClick: this.handleSubmitClick, - submitting: this.state.submitting, - })} - </Modal> - ); - } -} diff --git a/server/sonar-web/src/main/js/components/controls/Toggle.css b/server/sonar-web/src/main/js/components/controls/Toggle.css deleted file mode 100644 index 8ed10d83a2f..00000000000 --- a/server/sonar-web/src/main/js/components/controls/Toggle.css +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.button.boolean-toggle { - display: inline-block; - vertical-align: middle; - width: 48px; - height: var(--controlHeight); - padding: 1px; - border: 1px solid var(--gray80); - border-radius: var(--controlHeight); - box-sizing: border-box; - background-color: #fff; - cursor: pointer; - transition: all 0.3s ease; -} - -.button.boolean-toggle:hover { - background-color: #fff; -} - -.button.boolean-toggle:focus { - border-color: var(--blue); - background-color: #f6f6f6; -} - -.boolean-toggle-handle { - display: flex; - justify-content: center; - align-items: center; - width: 20px; - height: 20px; - border: 1px solid var(--gray80); - border-radius: 22px; - box-sizing: border-box; - background-color: #f6f6f6; - transition: - transform 0.3s cubic-bezier(0.87, -0.41, 0.19, 1.44), - border 0.3s ease; -} - -.boolean-toggle-handle > * { - opacity: 0; - transition: opacity 0.3s ease; -} - -.button.boolean-toggle-on { - border-color: var(--darkBlue); - background-color: var(--darkBlue); - color: var(--darkBlue); -} - -.button.boolean-toggle-on:hover { - background-color: var(--darkBlue); -} - -.button.boolean-toggle-on:focus { - background-color: var(--darkBlue); -} - -.button.boolean-toggle-on .boolean-toggle-handle { - border-color: #f6f6f6; - transform: translateX(var(--controlHeight)); -} - -.button.boolean-toggle-on .boolean-toggle-handle > * { - opacity: 1; -} diff --git a/server/sonar-web/src/main/js/components/controls/Toggle.tsx b/server/sonar-web/src/main/js/components/controls/Toggle.tsx deleted file mode 100644 index 6fe90a37c99..00000000000 --- a/server/sonar-web/src/main/js/components/controls/Toggle.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import * as React from 'react'; -import CheckIcon from '../icons/CheckIcon'; -import { Button } from './buttons'; -import './Toggle.css'; - -interface Props { - id?: string; - ariaLabel?: string; - disabled?: boolean; - name?: string; - onChange?: (value: boolean) => void; - value: boolean | string; -} - -export function getToggleValue(value: string | boolean) { - return typeof value === 'string' ? value === 'true' : value; -} - -export default class Toggle extends React.PureComponent<Props> { - getValue = () => { - const { value } = this.props; - return getToggleValue(value); - }; - - handleClick = () => { - if (this.props.onChange) { - const value = this.getValue(); - this.props.onChange(!value); - } - }; - - render() { - const { ariaLabel, disabled, name, id } = this.props; - const value = this.getValue(); - const className = classNames('boolean-toggle', { 'boolean-toggle-on': value }); - - return ( - <Button - id={id} - className={className} - disabled={disabled} - aria-label={ariaLabel} - name={name} - onClick={this.handleClick} - role="switch" - aria-checked={value} - > - <div className="boolean-toggle-handle"> - <CheckIcon size={12} /> - </div> - </Button> - ); - } -} diff --git a/server/sonar-web/src/main/js/components/controls/Tooltip.tsx b/server/sonar-web/src/main/js/components/controls/Tooltip.tsx index cfe00ea8649..4119d20a857 100644 --- a/server/sonar-web/src/main/js/components/controls/Tooltip.tsx +++ b/server/sonar-web/src/main/js/components/controls/Tooltip.tsx @@ -406,7 +406,7 @@ export class TooltipInner extends React.Component<TooltipProps, State> { return ( <div className={classNames(`${classNameSpace}-inner sw-font-sans`, classNameInner, { - hidden: !isVisible, + 'sw-hidden': !isVisible, })} id={this.id} role="tooltip" diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/BoxedGroupAccordion-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/BoxedGroupAccordion-test.tsx deleted file mode 100644 index d73c06e7717..00000000000 --- a/server/sonar-web/src/main/js/components/controls/__tests__/BoxedGroupAccordion-test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import * as React from 'react'; -import { renderComponent } from '../../../helpers/testReactTestingUtils'; -import BoxedGroupAccordion from '../BoxedGroupAccordion'; - -it('should behave correctly', async () => { - const user = userEvent.setup(); - renderDeliveryAccordion(); - expect(screen.queryByText('children')).not.toBeInTheDocument(); - await user.click(screen.getByRole('button', { expanded: false })); - expect(screen.getByText('children')).toBeInTheDocument(); -}); - -it('should render header correctly', () => { - renderDeliveryAccordion(() => <div>header</div>); - expect(screen.getByText('header')).toBeInTheDocument(); -}); - -function renderDeliveryAccordion(renderHeader?: () => React.ReactNode) { - function AccordionTest() { - const [open, setOpen] = React.useState(false); - - return ( - <BoxedGroupAccordion - onClick={() => setOpen(!open)} - open={open} - title="test" - renderHeader={renderHeader} - > - <div>children</div> - </BoxedGroupAccordion> - ); - } - - return renderComponent(<AccordionTest />); -} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ButtonToggle-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/ButtonToggle-test.tsx deleted file mode 100644 index e02771c5e91..00000000000 --- a/server/sonar-web/src/main/js/components/controls/__tests__/ButtonToggle-test.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import * as React from 'react'; -import { renderComponent } from '../../../helpers/testReactTestingUtils'; -import ButtonToggle, { ButtonToggleProps } from '../ButtonToggle'; - -it('should behave properly', async () => { - const onCheck = jest.fn(); - const user = userEvent.setup(); - - render({ onCheck }); - expect(screen.getAllByRole('button')).toHaveLength(3); - - await user.click(screen.getByRole('button', { name: 'first' })); - expect(onCheck).toHaveBeenCalledWith('one'); - - await user.click(screen.getByRole('button', { name: 'second' })); - expect(onCheck).not.toHaveBeenLastCalledWith('two'); -}); - -it('should behave properly when disabled', async () => { - const onCheck = jest.fn(); - const user = userEvent.setup(); - - render({ disabled: true, onCheck }); - - await user.click(screen.getByRole('button', { name: 'first' })); - expect(onCheck).not.toHaveBeenCalled(); -}); - -function render(props?: Partial<ButtonToggleProps>) { - renderComponent( - <ButtonToggle - label="test-label" - onCheck={jest.fn()} - disabled={false} - options={[ - { value: 'one', label: 'first' }, - { value: 'two', label: 'second' }, - { value: 'tree', label: 'third' }, - ]} - value="two" - {...props} - />, - ); -} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.tsx deleted file mode 100644 index 8883194940d..00000000000 --- a/server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -import * as React from 'react'; -import { renderComponent } from '../../../helpers/testReactTestingUtils'; -import Checkbox from '../Checkbox'; - -describe.each([ - { children: null, describtion: 'with no children' }, - { children: <a>child</a>, describtion: 'with children' }, -])('Checkbox $describtion', ({ children }) => { - it('should call check function', async () => { - const user = userEvent.setup(); - const onCheck = jest.fn(); - const rerender = renderCheckbox({ - label: 'me', - children, - onCheck, - checked: false, - title: 'title', - }); - await user.click(screen.getByRole('checkbox', { name: 'me' })); - expect(onCheck).toHaveBeenCalledWith(true, undefined); - expect(screen.getByTitle('title')).toBeInTheDocument(); - rerender({ checked: true }); - await user.click(screen.getByRole('checkbox', { name: 'me' })); - expect(onCheck).toHaveBeenCalledWith(false, undefined); - }); - - it('should accept partial state', () => { - renderCheckbox({ label: 'me', thirdState: true, children, checked: false }); - expect(screen.getByRole('checkbox', { name: 'me' })).not.toBeChecked(); - }); - - it('should render loading state', () => { - renderCheckbox({ label: 'me', children, loading: true }); - expect(screen.getByTestId('spinner')).toMatchSnapshot(); - }); -}); - -it('should render the checkbox on the right', () => { - renderCheckbox({ label: 'me', children: <a>child</a>, right: true }); - expect(screen.getByRole('checkbox', { name: 'me' })).toMatchSnapshot(); -}); - -function renderCheckbox(override?: Partial<Checkbox['props']>) { - const { rerender } = renderComponent(<Checkbox checked onCheck={jest.fn()} {...override} />); - return function (reoverride?: Partial<Checkbox['props']>) { - rerender(<Checkbox checked onCheck={jest.fn()} {...override} {...reoverride} />); - }; -} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.tsx index ac0551aa375..fe03b68b10b 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.tsx +++ b/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.tsx @@ -80,64 +80,3 @@ describe('ListFooter', () => { return renderComponent(<ListFooter count={3} loadMore={jest.fn()} total={5} {...props} />); } }); - -// Once the MIUI buttons become the norm, we can use only the above test "suite" and drop this one. -describe('ListFooter using MIUI buttons', () => { - describe('rendering', () => { - it('should render correctly when loading', async () => { - renderListFooter({ loading: true }); - expect(await screen.findByText('loading')).toBeInTheDocument(); - }); - - it('should not render if there are neither loadmore nor reload props', () => { - renderListFooter({ loadMore: undefined, reload: undefined }); - expect(screen.queryByRole('button')).not.toBeInTheDocument(); - }); - - it.each([ - [undefined, 60, 30, true], - [undefined, 45, 30, false], - [undefined, 60, undefined, false], - [60, 60, 30, false], - ])( - 'should handle showing load more button based on total, count and pageSize', - (total, count, pageSize, expected) => { - renderListFooter({ total, count, pageSize }); - - /* eslint-disable jest/no-conditional-in-test */ - /* eslint-disable jest/no-conditional-expect */ - if (expected) { - expect(screen.getByRole('button')).toBeInTheDocument(); - } else { - expect(screen.queryByRole('button')).not.toBeInTheDocument(); - } - /* eslint-enable jest/no-conditional-in-test */ - /* eslint-enable jest/no-conditional-expect */ - }, - ); - }); - - it('should properly call load more callback', async () => { - const user = userEvent.setup(); - const loadMore = jest.fn(); - renderListFooter({ loadMore }); - - await user.click(screen.getByRole('button')); - expect(loadMore).toHaveBeenCalled(); - }); - - it('should properly call reload callback', async () => { - const user = userEvent.setup(); - const reload = jest.fn(); - renderListFooter({ needReload: true, reload }); - - await user.click(screen.getByRole('button')); - expect(reload).toHaveBeenCalled(); - }); - - function renderListFooter(props: Partial<ListFooterProps> = {}) { - return renderComponent( - <ListFooter count={3} loadMore={jest.fn()} total={5} useMIUIButtons {...props} />, - ); - } -}); diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/RadioCard-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/RadioCard-test.tsx deleted file mode 100644 index 145f33ac1fe..00000000000 --- a/server/sonar-web/src/main/js/components/controls/__tests__/RadioCard-test.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import userEvent from '@testing-library/user-event'; -import * as React from 'react'; -import { renderComponent } from '../../../helpers/testReactTestingUtils'; -import { byRole } from '../../../helpers/testSelector'; -import { FCProps } from '../../../types/misc'; -import RadioCard from '../RadioCard'; - -describe('RadioCard component', () => { - it('renders & handles selection', async () => { - const user = userEvent.setup(); - const onClick = jest.fn(); - renderRadioCard({ onClick }); - - const card = byRole('radio').get(); - - expect(card).toBeInTheDocument(); - expect(byRole('heading', { name: 'body' }).get()).toBeInTheDocument(); - - // Keyboard selection - await user.keyboard('{Tab}'); - await user.keyboard('{Enter}'); - expect(onClick).toHaveBeenCalledTimes(1); - - // Mouse selection - await user.click(card); - expect(onClick).toHaveBeenCalledTimes(2); - }); -}); - -function renderRadioCard(overrides: Partial<FCProps<typeof RadioCard>>) { - return renderComponent( - <RadioCard title="Radio Card" {...overrides}> - <h3>body</h3> - </RadioCard>, - ); -} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Checkbox-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Checkbox-test.tsx.snap deleted file mode 100644 index 9d0ad090f98..00000000000 --- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Checkbox-test.tsx.snap +++ /dev/null @@ -1,55 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Checkbox with children should render loading state 1`] = ` -<i - aria-live="polite" - class="spinner is-loading" - data-testid="spinner" -> - <span - class="sw-sr-only" - > - loading - </span> -</i> -`; - -exports[`Checkbox with no children should render loading state 1`] = ` -<i - aria-live="polite" - class="spinner is-loading" - data-testid="spinner" -> - <span - class="sw-sr-only" - > - me - </span> -</i> -`; - -exports[`should render the checkbox on the right 1`] = ` -<a - aria-checked="true" - aria-label="me" - class="link-checkbox" - href="#" - role="checkbox" -> - <a> - child - </a> - <div - class="sw-overflow-hidden sw-relative" - > - <i - aria-live="polite" - class="spinner sw-sr-only sw-left-[-10000px]" - data-testid="spinner" - /> - </div> - <i - class="icon-checkbox icon-checkbox-checked" - /> -</a> -`; diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/clipboard-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/clipboard-test.tsx deleted file mode 100644 index f08fd6ba341..00000000000 --- a/server/sonar-web/src/main/js/components/controls/__tests__/clipboard-test.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { act, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import * as React from 'react'; -import { renderComponent } from '../../../helpers/testReactTestingUtils'; -import { - ClipboardBase, - ClipboardButton, - ClipboardButtonProps, - ClipboardIconButton, - ClipboardIconButtonProps, -} from '../clipboard'; - -beforeEach(() => { - jest.useFakeTimers(); -}); - -afterEach(() => { - jest.runOnlyPendingTimers(); - jest.useRealTimers(); -}); - -describe('ClipboardBase', () => { - it('should display correctly', () => { - renderClipboardBase(); - expect(screen.getByText('click to copy')).toBeInTheDocument(); - }); - - it('should allow its content to be copied', async () => { - const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); - renderClipboardBase(); - - await user.click(screen.getByRole('button')); - expect(await screen.findByText('copied')).toBeInTheDocument(); - - act(() => jest.runAllTimers()); - - expect(await screen.findByText('click to copy')).toBeInTheDocument(); - }); - - function renderClipboardBase(props: Partial<ClipboardBase['props']> = {}) { - return renderComponent( - <ClipboardBase {...props}> - {({ setCopyButton, copySuccess }) => ( - <span data-clipboard-text="foo" ref={setCopyButton} role="button"> - {copySuccess ? 'copied' : 'click to copy'} - </span> - )} - </ClipboardBase>, - ); - } -}); - -describe('ClipboardButton', () => { - it('should display correctly', () => { - renderClipboardButton(); - expect(screen.getByRole('button', { name: 'copy_to_clipboard' })).toBeInTheDocument(); - }); - - it('should render a custom label if provided', () => { - renderClipboardButton({ children: 'custom label' }); - expect(screen.getByRole('button', { name: 'copy_to_clipboard' })).toBeInTheDocument(); - expect(screen.getByText('custom label')).toBeInTheDocument(); - }); - - it('should render a custom aria-label if provided', () => { - renderClipboardButton({ 'aria-label': 'custom label' }); - expect(screen.getByRole('button', { name: 'custom label' })).toBeInTheDocument(); - }); - - function renderClipboardButton(props: Partial<ClipboardButtonProps> = {}) { - return renderComponent(<ClipboardButton copyValue="foo" {...props} />); - } -}); - -describe('ClipboardIconButton', () => { - it('should display correctly', () => { - renderClipboardIconButton(); - expect(screen.getByRole('button', { name: 'copy_to_clipboard' })).toBeInTheDocument(); - }); - - it('should render a custom aria-label if provided', () => { - renderClipboardIconButton({ 'aria-label': 'custom label' }); - expect(screen.getByRole('button', { name: 'custom label' })).toBeInTheDocument(); - }); - - function renderClipboardIconButton(props: Partial<ClipboardIconButtonProps> = {}) { - return renderComponent(<ClipboardIconButton copyValue="foo" {...props} />); - } -}); diff --git a/server/sonar-web/src/main/js/components/controls/clipboard.tsx b/server/sonar-web/src/main/js/components/controls/clipboard.tsx deleted file mode 100644 index 3075730dabe..00000000000 --- a/server/sonar-web/src/main/js/components/controls/clipboard.tsx +++ /dev/null @@ -1,165 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import Clipboard from 'clipboard'; -import * as React from 'react'; -import { translate } from '../../helpers/l10n'; -import CopyIcon from '../icons/CopyIcon'; -import Tooltip from './Tooltip'; -import { Button, ButtonIcon } from './buttons'; - -export interface State { - copySuccess: boolean; -} - -interface RenderProps { - setCopyButton: (node: HTMLElement | null) => void; - copySuccess: boolean; -} - -interface Props { - children: (props: RenderProps) => React.ReactNode; -} - -export class ClipboardBase extends React.PureComponent<Props, State> { - private clipboard?: Clipboard; - private copyButton?: HTMLElement | null; - mounted = false; - state: State = { copySuccess: false }; - - componentDidMount() { - this.mounted = true; - if (this.copyButton) { - this.clipboard = new Clipboard(this.copyButton); - this.clipboard.on('success', this.handleSuccessCopy); - } - } - - componentDidUpdate() { - if (this.clipboard) { - this.clipboard.destroy(); - } - if (this.copyButton) { - this.clipboard = new Clipboard(this.copyButton); - this.clipboard.on('success', this.handleSuccessCopy); - } - } - - componentWillUnmount() { - this.mounted = false; - if (this.clipboard) { - this.clipboard.destroy(); - } - } - - setCopyButton = (node: HTMLElement | null) => { - this.copyButton = node; - }; - - handleSuccessCopy = () => { - if (this.mounted) { - this.setState({ copySuccess: true }, () => { - if (this.copyButton) { - this.copyButton.focus(); - } - setTimeout(() => { - if (this.mounted) { - this.setState({ copySuccess: false }); - } - }, 1000); - }); - } - }; - - render() { - return this.props.children({ - setCopyButton: this.setCopyButton, - copySuccess: this.state.copySuccess, - }); - } -} - -export interface ClipboardButtonProps { - 'aria-label'?: string; - className?: string; - copyValue: string; - children?: React.ReactNode; -} - -export function ClipboardButton({ - className, - children, - copyValue, - 'aria-label': ariaLabel, -}: ClipboardButtonProps) { - return ( - <ClipboardBase> - {({ setCopyButton, copySuccess }) => ( - <Tooltip overlay={translate('copied_action')} visible={copySuccess}> - <Button - className={classNames('no-select', className)} - data-clipboard-text={copyValue} - innerRef={setCopyButton} - aria-label={ - copySuccess ? translate('copied_action') : ariaLabel ?? translate('copy_to_clipboard') - } - > - {children ?? ( - <> - <CopyIcon className="little-spacer-right" /> - {translate('copy')} - </> - )} - </Button> - </Tooltip> - )} - </ClipboardBase> - ); -} - -export interface ClipboardIconButtonProps { - 'aria-label'?: string; - className?: string; - copyValue: string; -} - -export function ClipboardIconButton(props: ClipboardIconButtonProps) { - const { 'aria-label': ariaLabel, className, copyValue } = props; - return ( - <ClipboardBase> - {({ setCopyButton, copySuccess }) => { - return ( - <ButtonIcon - aria-label={ - copySuccess ? translate('copied_action') : ariaLabel ?? translate('copy_to_clipboard') - } - className={classNames('no-select', className)} - data-clipboard-text={copyValue} - innerRef={setCopyButton} - tooltip={copySuccess ? translate('copied_action') : undefined} - tooltipProps={copySuccess ? { visible: true } : undefined} - > - <CopyIcon /> - </ButtonIcon> - ); - }} - </ClipboardBase> - ); -} diff --git a/server/sonar-web/src/main/js/components/devops-platform/AlmSettingsInstanceSelector.tsx b/server/sonar-web/src/main/js/components/devops-platform/AlmSettingsInstanceSelector.tsx index 2aae7daac97..035b70bd705 100644 --- a/server/sonar-web/src/main/js/components/devops-platform/AlmSettingsInstanceSelector.tsx +++ b/server/sonar-web/src/main/js/components/devops-platform/AlmSettingsInstanceSelector.tsx @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { InputSelect, LabelValueSelectOption } from 'design-system'; +import { InputSelect, LabelValueSelectOption, Note } from 'design-system'; import * as React from 'react'; import { OptionProps, SingleValueProps, components } from 'react-select'; import { translate } from '../../helpers/l10n'; @@ -39,7 +39,7 @@ function customOptions(instance: AlmSettingsInstance) { return instance.url ? ( <> <span>{instance.key} — </span> - <span className="text-muted">{instance.url}</span> + <Note>{instance.url}</Note> </> ) : ( <span>{instance.key}</span> diff --git a/server/sonar-web/src/main/js/components/icons/BranchIcon.tsx b/server/sonar-web/src/main/js/components/icons/BranchIcon.tsx deleted file mode 100644 index 1a392e60a84..00000000000 --- a/server/sonar-web/src/main/js/components/icons/BranchIcon.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { colors } from '../../app/theme'; -import Icon, { IconProps } from './Icon'; - -export default function BranchIcon({ fill, ...iconProps }: IconProps) { - return ( - <Icon {...iconProps}> - <path - d="M12.5 6.5c0-1.1-.9-2-2-2s-2 .9-2 2c0 .8.5 1.5 1.2 1.8-.3.6-.7 1.1-1.2 1.4-.9.5-1.9.5-2.5.4V4c.9-.2 1.5-1 1.5-1.9 0-1.1-.9-2-2-2s-2 .9-2 2C3.5 3 4.1 3.8 5 4v8c-.9.2-1.5 1-1.5 1.9 0 1.1.9 2 2 2s2-.9 2-2c0-.9-.6-1.7-1.5-1.9v-1c.2 0 .5.1.7.1.7 0 1.5-.1 2.2-.6.8-.5 1.4-1.2 1.7-2.1 1.1 0 1.9-.9 1.9-1.9zm-8-4.4c0-.6.4-1 1-1s1 .4 1 1-.4 1-1 1-1-.5-1-1zm2 11.9c0 .6-.4 1-1 1s-1-.4-1-1 .4-1 1-1 1 .4 1 1zm4-6.5c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z" - style={{ fill: fill || colors.primary }} - /> - </Icon> - ); -} diff --git a/server/sonar-web/src/main/js/components/icons/CheckIcon.tsx b/server/sonar-web/src/main/js/components/icons/CheckIcon.tsx deleted file mode 100644 index 1015660e265..00000000000 --- a/server/sonar-web/src/main/js/components/icons/CheckIcon.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import Icon, { IconProps } from './Icon'; - -export default function CheckIcon({ fill = 'currentColor', ...iconProps }: IconProps) { - return ( - <Icon {...iconProps}> - <path - d="M14.92 4.804q0 0.357-0.25 0.607l-7.679 7.679q-0.25 0.25-0.607 0.25t-0.607-0.25l-4.446-4.446q-0.25-0.25-0.25-0.607t0.25-0.607l1.214-1.214q0.25-0.25 0.607-0.25t0.607 0.25l2.625 2.634 5.857-5.866q0.25-0.25 0.607-0.25t0.607 0.25l1.214 1.214q0.25 0.25 0.25 0.607z" - style={{ fill }} - /> - </Icon> - ); -} diff --git a/server/sonar-web/src/main/js/components/icons/CopyIcon.tsx b/server/sonar-web/src/main/js/components/icons/CopyIcon.tsx deleted file mode 100644 index 0bfbb29d0b3..00000000000 --- a/server/sonar-web/src/main/js/components/icons/CopyIcon.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import Icon, { IconProps } from './Icon'; - -export default function CopyIcon({ fill = 'currentColor', ...iconProps }: IconProps) { - return ( - <Icon {...iconProps}> - <g fill={fill} fillRule="nonzero"> - <path d="M2.931 15.005V3H2v13h9v-.995z" /> - <path d="M10 4.015h3V14H4V1h6v3.015zM9 8V6H8v2H6v1h2v2h1V9h2V8H9z" /> - <path d="M11 1v2h2a2.151 2.151 0 0 0-2-2z" /> - </g> - </Icon> - ); -} diff --git a/server/sonar-web/src/main/js/components/icons/HelpIcon.tsx b/server/sonar-web/src/main/js/components/icons/HelpIcon.tsx deleted file mode 100644 index ab3629a43a3..00000000000 --- a/server/sonar-web/src/main/js/components/icons/HelpIcon.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import Icon, { IconProps } from './Icon'; - -interface Props extends IconProps { - fillInner?: string; -} - -export default function HelpIcon({ fill = 'currentColor', fillInner, ...iconProps }: Props) { - return ( - <Icon {...iconProps}> - <path - d="M9.167 12.375v-1.75a.284.284 0 00-.082-.21.284.284 0 00-.21-.082h-1.75a.284.284 0 00-.21.082.284.284 0 00-.082.21v1.75c0 .085.028.155.082.21a.284.284 0 00.21.082h1.75a.284.284 0 00.21-.082.284.284 0 00.082-.21zM11.5 6.25c0-.535-.169-1.03-.506-1.486a3.452 3.452 0 00-1.262-1.057 3.462 3.462 0 00-1.55-.374c-1.476 0-2.603.647-3.381 1.942-.091.146-.067.273.073.383l1.203.911c.042.036.1.055.173.055a.269.269 0 00.228-.11c.322-.413.583-.692.784-.838.206-.146.468-.219.784-.219.291 0 .551.079.779.237.228.158.342.337.342.538 0 .23-.061.416-.183.556-.121.14-.328.276-.62.41a3.13 3.13 0 00-1.052.788c-.32.356-.479.737-.479 1.144v.328c0 .085.028.155.082.21a.284.284 0 00.21.082h1.75a.284.284 0 00.21-.082.284.284 0 00.082-.21c0-.115.065-.266.196-.45a1.54 1.54 0 01.496-.452c.195-.11.344-.196.447-.26a3.84 3.84 0 00.42-.319c.175-.149.31-.294.405-.437a2.407 2.407 0 00.369-1.29zM15 8c0 1.27-.313 2.441-.939 3.514a6.969 6.969 0 01-2.547 2.547A6.848 6.848 0 018 15a6.848 6.848 0 01-3.514-.939 6.969 6.969 0 01-2.547-2.547A6.848 6.848 0 011 8c0-1.27.313-2.441.939-3.514A6.969 6.969 0 014.486 1.94 6.848 6.848 0 018 1c1.27 0 2.441.313 3.514.939a6.969 6.969 0 012.547 2.547A6.848 6.848 0 0115 8z" - fill={fill} - /> - {fillInner && ( - <path - d="M9.167 12.375v-1.75a.284.284 0 00-.082-.21.284.284 0 00-.21-.082h-1.75a.284.284 0 00-.21.082.284.284 0 00-.082.21v1.75c0 .085.028.155.082.21a.284.284 0 00.21.082h1.75a.284.284 0 00.21-.082.284.284 0 00.082-.21zM11.5 6.25c0-.535-.169-1.03-.506-1.486a3.452 3.452 0 00-1.262-1.057 3.462 3.462 0 00-1.55-.374c-1.476 0-2.603.647-3.381 1.942-.091.146-.067.273.073.383l1.203.911c.042.036.1.055.173.055a.269.269 0 00.228-.11c.322-.413.583-.692.784-.838.206-.146.468-.219.784-.219.291 0 .551.079.779.237.228.158.342.337.342.538 0 .23-.061.416-.183.556-.121.14-.328.276-.62.41a3.13 3.13 0 00-1.052.788c-.32.356-.479.737-.479 1.144v.328c0 .085.028.155.082.21a.284.284 0 00.21.082h1.75a.284.284 0 00.21-.082.284.284 0 00.082-.21c0-.115.065-.266.196-.45a1.54 1.54 0 01.496-.452c.195-.11.344-.196.447-.26a3.84 3.84 0 00.42-.319c.175-.149.31-.294.405-.437a2.407 2.407 0 00.369-1.29z" - fill={fillInner} - /> - )} - </Icon> - ); -} diff --git a/server/sonar-web/src/main/js/components/icons/ProjectLinkIcon.tsx b/server/sonar-web/src/main/js/components/icons/ProjectLinkIcon.tsx index 3c0af8dd946..e53096d3e0e 100644 --- a/server/sonar-web/src/main/js/components/icons/ProjectLinkIcon.tsx +++ b/server/sonar-web/src/main/js/components/icons/ProjectLinkIcon.tsx @@ -26,35 +26,24 @@ import { SyncIcon, } from '@primer/octicons-react'; import React, { FC } from 'react'; -import BugTrackerIcon from './BugTrackerIcon'; -import ContinuousIntegrationIcon from './ContinuousIntegrationIcon'; -import DetachIcon from './DetachIcon'; -import HouseIcon from './HouseIcon'; -import { IconProps } from './Icon'; -import SCMIcon from './SCMIcon'; interface ProjectLinkIconProps { type: string; - miui?: boolean; } -export default function ProjectLinkIcon({ - miui, - type, - ...iconProps -}: IconProps & ProjectLinkIconProps) { - const getIcon = (): FC<React.PropsWithChildren<IconProps | MIUIIconProps>> => { +export default function ProjectLinkIcon({ type, ...iconProps }: ProjectLinkIconProps) { + const getIcon = (): FC<React.PropsWithChildren<MIUIIconProps>> => { switch (type) { case 'issue': - return miui ? PulseIcon : BugTrackerIcon; + return PulseIcon; case 'homepage': - return miui ? HomeIcon : HouseIcon; + return HomeIcon; case 'ci': - return miui ? SyncIcon : ContinuousIntegrationIcon; + return SyncIcon; case 'scm': - return miui ? FileIcon : SCMIcon; + return FileIcon; default: - return miui ? LinkExternalIcon : DetachIcon; + return LinkExternalIcon; } }; diff --git a/server/sonar-web/src/main/js/components/issue/Issue.css b/server/sonar-web/src/main/js/components/issue/Issue.css index ebe0ca83fc6..c1951b70480 100644 --- a/server/sonar-web/src/main/js/components/issue/Issue.css +++ b/server/sonar-web/src/main/js/components/issue/Issue.css @@ -241,10 +241,6 @@ overflow: auto; } -.issue .badge-error { - background-color: var(--badgeRedBackgroundOnIssue); -} - .issue-message-box { background-color: var(--issueBgColor); border: 2px solid transparent; diff --git a/server/sonar-web/src/main/js/components/new-code-definition/NCDAutoUpdateMessage.tsx b/server/sonar-web/src/main/js/components/new-code-definition/NCDAutoUpdateMessage.tsx index bdc4e6e66dd..e1e8dc53d1e 100644 --- a/server/sonar-web/src/main/js/components/new-code-definition/NCDAutoUpdateMessage.tsx +++ b/server/sonar-web/src/main/js/components/new-code-definition/NCDAutoUpdateMessage.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Banner, Link } from 'design-system'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import { MessageTypes, checkMessageDismissed, setMessageDismissed } from '../../api/messages'; @@ -26,8 +27,6 @@ import { NEW_CODE_PERIOD_CATEGORY } from '../../apps/settings/constants'; import { queryToSearch } from '../../helpers/urls'; import { useNewCodeDefinitionQuery } from '../../queries/newCodeDefinition'; import { Component } from '../../types/types'; -import Link from '../common/Link'; -import DismissableAlertComponent from '../ui/DismissableAlertComponent'; import { PreviouslyNonCompliantNCD, isGlobalOrProjectAdmin, @@ -117,26 +116,23 @@ function NCDAutoUpdateMessage(props: Readonly<NCDAutoUpdateMessageProps>) { : 'new_code_definition.auto_update.project.message'; return ( - <DismissableAlertComponent - onDismiss={handleBannerDismiss} - variant="info" - display="banner" - bannerClassName="sw-mb-0" - > - <FormattedMessage - id={bannerMessageId} - values={{ - date: new Date(updatedAt).toLocaleDateString(), - days: value, - link: ( - <Link to={ncdReviewLinkTo}> - {intl.formatMessage({ id: 'new_code_definition.auto_update.review_link' })} - </Link> - ), - previousDays: previousNonCompliantValue, - }} - /> - </DismissableAlertComponent> + <Banner onDismiss={handleBannerDismiss} variant="info"> + <p> + <FormattedMessage + id={bannerMessageId} + values={{ + date: new Date(updatedAt).toLocaleDateString(), + days: value, + link: ( + <Link to={ncdReviewLinkTo}> + {intl.formatMessage({ id: 'new_code_definition.auto_update.review_link' })} + </Link> + ), + previousDays: previousNonCompliantValue, + }} + /> + </p> + </Banner> ); } diff --git a/server/sonar-web/src/main/js/components/new-code-definition/NewCodeDefinitionDaysOption.tsx b/server/sonar-web/src/main/js/components/new-code-definition/NewCodeDefinitionDaysOption.tsx index 0b2be2cfa3a..b47d9bb154e 100644 --- a/server/sonar-web/src/main/js/components/new-code-definition/NewCodeDefinitionDaysOption.tsx +++ b/server/sonar-web/src/main/js/components/new-code-definition/NewCodeDefinitionDaysOption.tsx @@ -17,7 +17,13 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { FlagErrorIcon, InputField, Note, SelectionCard } from 'design-system'; +import { + DismissableFlagMessage, + FlagErrorIcon, + InputField, + Note, + SelectionCard, +} from 'design-system'; import { noop } from 'lodash'; import * as React from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; @@ -30,8 +36,7 @@ import { } from '../../helpers/new-code-definition'; import { isDefined } from '../../helpers/types'; import { NewCodeDefinitionType } from '../../types/new-code-definition'; -import DocLink from '../common/DocLink'; -import DismissableAlertComponent from '../ui/DismissableAlertComponent'; +import DocumentationLink from '../common/DocumentationLink'; import { NewCodeDefinitionLevels } from './utils'; export interface Props { @@ -146,9 +151,8 @@ export default function NewCodeDefinitionDaysOption(props: Props) { </Note> {shouldShowAutoUpdateBanner && ( - <DismissableAlertComponent + <DismissableFlagMessage variant="info" - display="inline" className="sw-mt-4 sw-max-w-[800px]" onDismiss={handleBannerDismiss} > @@ -161,13 +165,13 @@ export default function NewCodeDefinitionDaysOption(props: Props) { days: currentDaysValue, date: isDefined(updatedAt) && new Date(updatedAt).toLocaleDateString(), link: ( - <DocLink to="/project-administration/clean-as-you-code-settings/defining-new-code/#new-code-definition-options"> + <DocumentationLink to="/project-administration/clean-as-you-code-settings/defining-new-code/#new-code-definition-options"> {translate('learn_more')} - </DocLink> + </DocumentationLink> ), }} /> - </DismissableAlertComponent> + </DismissableFlagMessage> )} </div> )} diff --git a/server/sonar-web/src/main/js/components/new-code-definition/__tests__/NCDAutoUpdateMessage-test.tsx b/server/sonar-web/src/main/js/components/new-code-definition/__tests__/NCDAutoUpdateMessage-test.tsx index ae656c65f36..9a83f40f706 100644 --- a/server/sonar-web/src/main/js/components/new-code-definition/__tests__/NCDAutoUpdateMessage-test.tsx +++ b/server/sonar-web/src/main/js/components/new-code-definition/__tests__/NCDAutoUpdateMessage-test.tsx @@ -27,7 +27,7 @@ import NewCodeDefinitionServiceMock from '../../../api/mocks/NewCodeDefinitionSe import { mockComponent } from '../../../helpers/mocks/component'; import { mockLoggedInUser } from '../../../helpers/testMocks'; import { renderAppRoutes } from '../../../helpers/testReactTestingUtils'; -import { byLabelText, byText } from '../../../helpers/testSelector'; +import { byRole, byText } from '../../../helpers/testSelector'; import { NewCodeDefinitionType } from '../../../types/new-code-definition'; import { Component } from '../../../types/types'; import NCDAutoUpdateMessage from '../NCDAutoUpdateMessage'; @@ -64,7 +64,7 @@ describe('Global NCD update notification banner', () => { }); const ui = { - dismissButton: byLabelText('alert.dismiss'), + dismissButton: byRole('button', { name: 'dismiss' }), globalBannerContent: byText(/new_code_definition.auto_update.global.message/), reviewLink: byText('new_code_definition.auto_update.review_link'), adminNcdMessage: byText('Admin NCD'), @@ -92,9 +92,7 @@ describe('Global NCD update notification banner', () => { renderGlobalMessage(); expect(await ui.globalBannerContent.find()).toBeVisible(); const user = userEvent.setup(); - await act(async () => { - await user.click(ui.dismissButton.get()); - }); + await user.click(ui.dismissButton.get()); expect(ui.globalBannerContent.query()).not.toBeInTheDocument(); }); @@ -153,7 +151,7 @@ describe('Project NCD update notification banner', () => { }); const ui = { - dismissButton: byLabelText('alert.dismiss'), + dismissButton: byRole('button', { name: 'dismiss' }), projectBannerContent: byText(/new_code_definition.auto_update.project.message/), projectNcdMessage: byText('Project NCD'), reviewLink: byText('new_code_definition.auto_update.review_link'), @@ -189,9 +187,7 @@ describe('Project NCD update notification banner', () => { renderProjectMessage(component); expect(await ui.projectBannerContent.find()).toBeVisible(); const user = userEvent.setup(); - await act(async () => { - await user.click(ui.dismissButton.get()); - }); + await user.click(ui.dismissButton.get()); expect(ui.projectBannerContent.query()).not.toBeInTheDocument(); }); diff --git a/server/sonar-web/src/main/js/components/permissions/AllHoldersList.tsx b/server/sonar-web/src/main/js/components/permissions/AllHoldersList.tsx index c7779ade579..5350a3ea9c3 100644 --- a/server/sonar-web/src/main/js/components/permissions/AllHoldersList.tsx +++ b/server/sonar-web/src/main/js/components/permissions/AllHoldersList.tsx @@ -125,7 +125,7 @@ export default class AllHoldersList extends React.PureComponent<Props> { selectedPermission={selectedPermission} users={users} /> - <ListFooter count={count} loadMore={this.props.onLoadMore} total={total} useMIUIButtons /> + <ListFooter count={count} loadMore={this.props.onLoadMore} total={total} /> </> ); } diff --git a/server/sonar-web/src/main/js/components/rules/IssueTabViewer.tsx b/server/sonar-web/src/main/js/components/rules/IssueTabViewer.tsx index c72772b31cd..6fe3d31ec38 100644 --- a/server/sonar-web/src/main/js/components/rules/IssueTabViewer.tsx +++ b/server/sonar-web/src/main/js/components/rules/IssueTabViewer.tsx @@ -383,7 +383,7 @@ export class IssueTabViewer extends React.PureComponent<IssueTabViewerProps, Sta tabs.map((tab) => ( <div className={classNames({ - hidden: tab.key !== selectedTab.key, + 'sw-hidden': tab.key !== selectedTab.key, })} key={tab.key} > diff --git a/server/sonar-web/src/main/js/components/rules/RuleTabViewer.tsx b/server/sonar-web/src/main/js/components/rules/RuleTabViewer.tsx index aa7c18eea05..9096c29e98f 100644 --- a/server/sonar-web/src/main/js/components/rules/RuleTabViewer.tsx +++ b/server/sonar-web/src/main/js/components/rules/RuleTabViewer.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import classNames from 'classnames'; -import { ToggleButton } from 'design-system'; +import { ToggleButton, getTabId, getTabPanelId } from 'design-system'; import { cloneDeep, debounce, groupBy, isEqual } from 'lodash'; import * as React from 'react'; import { Location } from 'react-router-dom'; @@ -29,7 +29,6 @@ import { RuleDescriptionSections } from '../../apps/coding-rules/rule'; import { translate } from '../../helpers/l10n'; import { RuleDetails } from '../../types/types'; import { NoticeType } from '../../types/users'; -import { getTabId, getTabPanelId } from '../controls/BoxedTabs'; import withLocation from '../hoc/withLocation'; import MoreInfoRuleDescription from './MoreInfoRuleDescription'; import RuleDescription from './RuleDescription'; diff --git a/server/sonar-web/src/main/js/components/tags/TagsSelector.tsx b/server/sonar-web/src/main/js/components/tags/TagsSelector.tsx deleted file mode 100644 index 66e028c807b..00000000000 --- a/server/sonar-web/src/main/js/components/tags/TagsSelector.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { translate } from '../../helpers/l10n'; -import MultiSelect from '../common/MultiSelect'; -import './TagsList.css'; - -interface Props { - listSize: number; - onSearch: (query: string) => Promise<void>; - onSelect: (item: string) => void; - onUnselect: (item: string) => void; - selectedTags: string[]; - tags: string[]; -} - -export default function TagsSelector(props: Props) { - return ( - <MultiSelect - elements={props.tags} - listSize={props.listSize} - legend={translate('select_tags')} - onSearch={props.onSearch} - onSelect={props.onSelect} - onUnselect={props.onUnselect} - placeholder={translate('search.search_for_tags')} - selectedElements={props.selectedTags} - validateSearchInput={validateTag} - /> - ); -} - -export function validateTag(value: string) { - // Allow only a-z, 0-9, '+', '-', '#', '.' - return value.toLowerCase().replace(/[^-a-z0-9+#.]/gi, ''); -} diff --git a/server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx b/server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx index b057238a387..35dd791d8f4 100644 --- a/server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx @@ -25,6 +25,7 @@ import { HoverLink, LightLabel, LightPrimary, + Spinner, StandoutLink, SubTitle, Title, @@ -104,7 +105,7 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender DEFAULT_MAIN_BRANCH_NAME; if (loading) { - return <i aria-label={translate('loading')} className="spinner" />; + return <Spinner />; } if (!currentUserCanScanProject) { diff --git a/server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-it.tsx b/server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-it.tsx index 4814621ae0d..a2db9276d06 100644 --- a/server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-it.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/__tests__/TutorialSelection-it.tsx @@ -28,7 +28,7 @@ import UserTokensMock from '../../../api/mocks/UserTokensMock'; import { mockComponent } from '../../../helpers/mocks/component'; import { mockLoggedInUser } from '../../../helpers/testMocks'; import { renderApp } from '../../../helpers/testReactTestingUtils'; -import { byLabelText, byRole, byText } from '../../../helpers/testSelector'; +import { byRole, byText } from '../../../helpers/testSelector'; import { ComponentPropsType } from '../../../helpers/testUtils'; import { AlmKeys } from '../../../types/alm-settings'; import { Feature } from '../../../types/features'; @@ -69,7 +69,7 @@ beforeEach(() => { }); const ui = { - loading: byLabelText('loading'), + loading: byText('loading'), noScanRights: byText('onboarding.tutorial.no_scan_rights'), chooseTutorialLink: (mode: TutorialModes) => byRole('link', { name: `onboarding.tutorial.choose_method.${mode}` }), diff --git a/server/sonar-web/src/main/js/components/ui/Alert.tsx b/server/sonar-web/src/main/js/components/ui/Alert.tsx deleted file mode 100644 index 1416d309b47..00000000000 --- a/server/sonar-web/src/main/js/components/ui/Alert.tsx +++ /dev/null @@ -1,177 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { css } from '@emotion/react'; -import styled from '@emotion/styled'; -import classNames from 'classnames'; -import * as React from 'react'; -import { colors, sizes } from '../../app/theme'; -import { translate } from '../../helpers/l10n'; -import AlertErrorIcon from '../icons/AlertErrorIcon'; -import AlertSuccessIcon from '../icons/AlertSuccessIcon'; -import AlertWarnIcon from '../icons/AlertWarnIcon'; -import InfoIcon from '../icons/InfoIcon'; -import Spinner from './Spinner'; - -type AlertDisplay = 'banner' | 'inline' | 'block'; -export type AlertVariant = 'error' | 'warning' | 'success' | 'info' | 'loading'; - -export interface AlertProps { - display?: AlertDisplay; - variant: AlertVariant; - live?: boolean; -} - -const DOUBLE = 2; -const QUADRUPLE = 4; - -const alertInnerIsBannerMixin = () => css` - min-width: ${sizes.minPageWidth}; - max-width: ${sizes.maxPageWidth}; - margin-left: auto; - margin-right: auto; - padding-left: ${sizes.pagePadding}; - padding-right: ${sizes.pagePadding}; - box-sizing: border-box; -`; - -const StyledAlert = styled.div<{ - isInline: boolean; - color: string; - backGroundColor: string; - borderColor: string; - isBanner: boolean; -}>` - border: 1px solid; - border-radius: 2px; - margin-bottom: ${sizes.gridSize}; - border-color: ${({ borderColor }) => borderColor}; - background-color: ${({ backGroundColor }) => backGroundColor}; - color: ${({ color }) => color}; - display: ${({ isInline }) => (isInline ? 'inline-block' : 'block')}; - - :empty { - display: none; - } - - a, - .button-link { - border-color: ${colors.primarya40}; - } - - a: hover, - .button-link:hover { - border-color: ${colors.darkBlue}; - } - - & .alert-inner { - display: flex; - align-items: stretch; - ${({ isBanner }) => (isBanner ? alertInnerIsBannerMixin : null)} - } - - & .alert-icon { - flex: 0 0 auto; - display: flex; - justify-content: center; - align-items: center; - width: calc(${({ isBanner }) => (isBanner ? DOUBLE : QUADRUPLE)} * ${sizes.gridSize}); - border-right: ${({ isBanner }) => (!isBanner ? '1px solid' : 'none')}; - border-color: ${({ borderColor }) => borderColor}; - } - - & .alert-content { - flex: 1 1 auto; - overflow: auto; - text-align: left; - padding: ${sizes.gridSize} calc(2 * ${sizes.gridSize}); - } -`; - -function getAlertVariantInfo(variant: AlertVariant) { - const variantList = { - error: { - icon: ( - <AlertErrorIcon label={translate('alert.tooltip.error')} fill={colors.alertIconError} /> - ), - color: colors.alertTextError, - borderColor: colors.alertBorderError, - backGroundColor: colors.alertBackgroundError, - }, - warning: { - icon: ( - <AlertWarnIcon label={translate('alert.tooltip.warning')} fill={colors.alertIconWarning} /> - ), - color: colors.alertTextWarning, - borderColor: colors.alertBorderWarning, - backGroundColor: colors.alertBackgroundWarning, - }, - success: { - icon: ( - <AlertSuccessIcon - label={translate('alert.tooltip.success')} - fill={colors.alertIconSuccess} - /> - ), - color: colors.alertTextSuccess, - borderColor: colors.alertBorderSuccess, - backGroundColor: colors.alertBackgroundSuccess, - }, - info: { - icon: <InfoIcon label={translate('alert.tooltip.info')} fill={colors.alertIconInfo} />, - color: colors.alertTextInfo, - borderColor: colors.alertBorderInfo, - backGroundColor: colors.alertBackgroundInfo, - }, - loading: { - icon: <Spinner />, - color: colors.alertTextInfo, - borderColor: colors.alertBorderInfo, - backGroundColor: colors.alertBackgroundInfo, - }, - } as const; - - return variantList[variant]; -} - -export function Alert(props: AlertProps & React.HTMLAttributes<HTMLDivElement>) { - const { className, display, variant, children, live, ...domProps } = props; - const isInline = display === 'inline'; - const isBanner = display === 'banner'; - const variantInfo = getAlertVariantInfo(variant); - - return ( - <StyledAlert - className={classNames('alert', className)} - isBanner={isBanner} - isInline={isInline} - color={variantInfo.color} - borderColor={variantInfo.borderColor} - backGroundColor={variantInfo.backGroundColor} - {...domProps} - > - {children && ( - <div className="alert-inner"> - <div className="alert-icon">{variantInfo.icon}</div> - <div className="alert-content">{children}</div> - </div> - )} - </StyledAlert> - ); -} diff --git a/server/sonar-web/src/main/js/components/ui/CoverageRating.tsx b/server/sonar-web/src/main/js/components/ui/CoverageRating.tsx deleted file mode 100644 index b2140fca775..00000000000 --- a/server/sonar-web/src/main/js/components/ui/CoverageRating.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { colors } from '../../app/theme'; -import DonutChart from '../../components/charts/DonutChart'; - -const SIZE_TO_WIDTH_MAPPING = { small: 20, normal: 24, big: 40, huge: 60 }; -const SIZE_TO_THICKNESS_MAPPING = { small: 3, normal: 3, big: 3, huge: 4 }; - -const FULL_PERCENT = 100; - -type SIZE = 'small' | 'normal' | 'big' | 'huge'; - -export interface CoverageRatingProps { - muted?: boolean; - size?: SIZE; - value?: number | string; -} - -export default function CoverageRating({ - muted = false, - size = 'normal', - value, -}: CoverageRatingProps) { - let data = [{ value: FULL_PERCENT, fill: colors.gray71 }]; - let padAngle = 0; - - if (value != null) { - const numberValue = Number(value); - data = [ - { value: numberValue, fill: muted ? colors.gray71 : colors.success500 }, - { value: FULL_PERCENT - numberValue, fill: muted ? 'transparent' : colors.error500 }, - ]; - if (numberValue !== 0 && numberValue < FULL_PERCENT) { - padAngle = 0.1; // Same for all sizes, because it scales automatically - } - } - - const width = SIZE_TO_WIDTH_MAPPING[size]; - const thickness = SIZE_TO_THICKNESS_MAPPING[size]; - - return ( - <DonutChart - data={data} - height={width} - padAngle={padAngle} - thickness={thickness} - width={width} - /> - ); -} diff --git a/server/sonar-web/src/main/js/components/ui/DismissableAlert.css b/server/sonar-web/src/main/js/components/ui/DismissableAlert.css deleted file mode 100644 index fcc90edffe0..00000000000 --- a/server/sonar-web/src/main/js/components/ui/DismissableAlert.css +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.dismissable-alert-banner .dismissable-alert-content { - max-width: var(--maxPageWidth); -} - -.dismissable-alert-banner .button-icon { - height: var(--tinyControlHeight); - width: var(--tinyControlHeight); -} diff --git a/server/sonar-web/src/main/js/components/ui/DismissableAlert.tsx b/server/sonar-web/src/main/js/components/ui/DismissableAlert.tsx index 0b365831788..60f76ff541c 100644 --- a/server/sonar-web/src/main/js/components/ui/DismissableAlert.tsx +++ b/server/sonar-web/src/main/js/components/ui/DismissableAlert.tsx @@ -17,22 +17,22 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import classNames from 'classnames'; +import { Banner, Variant } from 'design-system'; import * as React from 'react'; -import { AlertProps } from '../../components/ui/Alert'; import { get, save } from '../../helpers/storage'; -import './DismissableAlert.css'; -import DismissableAlertComponent from './DismissableAlertComponent'; -export interface DismissableAlertProps extends AlertProps { +export interface DismissableAlertProps { alertKey: string; children?: React.ReactNode; className?: string; + variant: Variant; } export const DISMISSED_ALERT_STORAGE_KEY = 'sonarqube.dismissed_alert'; export default function DismissableAlert(props: DismissableAlertProps) { - const { alertKey, children } = props; + const { alertKey, children, className, variant } = props; const [show, setShow] = React.useState(false); React.useEffect(() => { @@ -47,14 +47,15 @@ export default function DismissableAlert(props: DismissableAlertProps) { }; return !show ? null : ( - <DismissableAlertComponent + <Banner onDismiss={() => { hideAlert(); setShow(false); }} - {...props} + className={classNames('sw-w-full', className)} + variant={variant} > {children} - </DismissableAlertComponent> + </Banner> ); } diff --git a/server/sonar-web/src/main/js/components/ui/DismissableAlertComponent.tsx b/server/sonar-web/src/main/js/components/ui/DismissableAlertComponent.tsx deleted file mode 100644 index f2bbe41acf4..00000000000 --- a/server/sonar-web/src/main/js/components/ui/DismissableAlertComponent.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import * as React from 'react'; -import { translate } from '../../helpers/l10n'; -import { ButtonIcon } from '../controls/buttons'; -import ClearIcon from '../icons/ClearIcon'; -import { Alert, AlertProps } from './Alert'; - -export interface DismissableAlertComponentProps extends AlertProps { - bannerClassName?: string; - className?: string; - children: React.ReactNode; - onDismiss: () => void; -} - -export default function DismissableAlertComponent(props: DismissableAlertComponentProps) { - const { bannerClassName, className, display = 'banner', variant, children, onDismiss } = props; - - return ( - <div className={classNames('dismissable-alert-wrapper', className)}> - <Alert - className={classNames(`dismissable-alert-${display}`, bannerClassName)} - display={display} - variant={variant} - > - <div className="display-flex-center dismissable-alert-content"> - <div className="flex-1">{children}</div> - <ButtonIcon aria-label={translate('alert.dismiss')} onClick={onDismiss}> - <ClearIcon size={12} thin /> - </ButtonIcon> - </div> - </Alert> - </div> - ); -} diff --git a/server/sonar-web/src/main/js/components/ui/DuplicationsRating.css b/server/sonar-web/src/main/js/components/ui/DuplicationsRating.css deleted file mode 100644 index d96ca84cadc..00000000000 --- a/server/sonar-web/src/main/js/components/ui/DuplicationsRating.css +++ /dev/null @@ -1,171 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.duplications-rating { - position: relative; - display: inline-flex; - vertical-align: top; - justify-content: center; - align-items: center; - width: var(--controlHeight); - height: var(--controlHeight); - border: 3px solid var(--orange); - border-radius: var(--controlHeight); - box-sizing: border-box; -} - -.duplications-rating-small { - width: 24px; - height: 24px; - border-width: 3px; -} - -.duplications-rating-big { - width: 40px; - height: 40px; - border-width: 3px; -} - -.duplications-rating-huge { - width: 60px; - height: 60px; - border-width: 4px; - border-radius: 30px; -} - -.duplications-rating-muted { - border-color: var(--gray71) !important; -} - -.duplications-rating-muted:after { - background-color: var(--gray71) !important; -} - -.duplications-rating:after { - border-radius: var(--controlHeight); - content: ''; -} - -.duplications-rating-A { - border-color: var(--success500); -} - -.duplications-rating-A:after { - display: none; -} - -.duplications-rating-B { - border-color: var(--successVariant); -} - -.duplications-rating-B:after { - width: 6px; - height: 6px; - background-color: var(--successVariant); -} - -.duplications-rating-small.duplications-rating-B:after { - width: 2px; - height: 2px; -} - -.duplications-rating-big.duplications-rating-B:after { - width: var(--smallFontSize); - height: var(--smallFontSize); -} - -.duplications-rating-huge.duplications-rating-B:after { - width: 18px; - height: 18px; -} - -.duplications-rating-C { - border-color: var(--warningVariant); -} - -.duplications-rating-C:after { - width: 8px; - height: 8px; - background-color: var(--warningVariant); -} - -.duplications-rating-small.duplications-rating-C:after { - width: 6px; - height: 6px; -} - -.duplications-rating-big.duplications-rating-C:after { - width: 16px; - height: 16px; -} - -.duplications-rating-huge.duplications-rating-C:after { - width: var(--controlHeight); - height: var(--controlHeight); -} - -.duplications-rating-D { - border-color: var(--warningAccent); -} - -.duplications-rating-D:after { - width: var(--smallFontSize); - height: var(--smallFontSize); - background-color: var(--warningAccent); -} - -.duplications-rating-small.duplications-rating-D:after { - width: 8px; - height: 8px; -} - -.duplications-rating-big.duplications-rating-D:after { - width: var(--controlHeight); - height: var(--controlHeight); -} - -.duplications-rating-huge.duplications-rating-D:after { - width: 36px; - height: 36px; -} - -.duplications-rating-E { - border-color: var(--error500); -} - -.duplications-rating-E:after { - width: 14px; - height: 14px; - background-color: var(--error500); -} - -.duplications-rating-small.duplications-rating-E:after { - width: 10px; - height: 10px; -} - -.duplications-rating-big.duplications-rating-E:after { - width: 28px; - height: 28px; -} - -.duplications-rating-huge.duplications-rating-E:after { - width: 42px; - height: 42px; -} diff --git a/server/sonar-web/src/main/js/components/ui/DuplicationsRating.tsx b/server/sonar-web/src/main/js/components/ui/DuplicationsRating.tsx deleted file mode 100644 index f65204196f0..00000000000 --- a/server/sonar-web/src/main/js/components/ui/DuplicationsRating.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import { inRange } from 'lodash'; -import * as React from 'react'; -import './DuplicationsRating.css'; - -interface Props { - muted?: boolean; - size?: 'small' | 'normal' | 'big' | 'huge'; - value?: number; -} - -export default function DuplicationsRating({ muted = false, size = 'normal', value }: Props) { - const className = classNames('duplications-rating', { - 'duplications-rating-small': size === 'small', - 'duplications-rating-big': size === 'big', - 'duplications-rating-huge': size === 'huge', - 'duplications-rating-muted': muted || value == null || isNaN(value), - 'duplications-rating-A': inRange(value || 0, 0, 3), - 'duplications-rating-B': inRange(value || 0, 3, 5), - 'duplications-rating-C': inRange(value || 0, 5, 10), - 'duplications-rating-D': inRange(value || 0, 10, 20), - 'duplications-rating-E': (value || 0) >= 20, - }); - - return <div className={className} />; -} diff --git a/server/sonar-web/src/main/js/components/ui/GenericAvatar.tsx b/server/sonar-web/src/main/js/components/ui/GenericAvatar.tsx deleted file mode 100644 index 7d15fbdb111..00000000000 --- a/server/sonar-web/src/main/js/components/ui/GenericAvatar.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import { getTextColor, stringToColor } from 'design-system'; -import * as React from 'react'; - -interface Props { - className?: string; - name: string; - round?: boolean; - size: number; -} - -export default function GenericAvatar({ className, name, round, size }: Props) { - const color = stringToColor(name); - - let text = ''; - const words = name.split(/\s+/).filter((word) => word.length > 0); - if (words.length >= 2) { - text = words[0][0] + words[1][0]; - } else if (name.length > 0) { - text = name[0]; - } - - return ( - <div - className={classNames(className, 'rounded')} - style={{ - backgroundColor: color, - borderRadius: round ? '50%' : undefined, - color: getTextColor(color), - display: 'inline-block', - fontSize: Math.min(size / 2, 14), - fontWeight: 'normal', - height: size, - lineHeight: `${size}px`, - textAlign: 'center', - verticalAlign: 'top', - width: size, - }} - > - {text.toUpperCase()} - </div> - ); -} diff --git a/server/sonar-web/src/main/js/components/ui/LegacyAvatar.tsx b/server/sonar-web/src/main/js/components/ui/LegacyAvatar.tsx deleted file mode 100644 index 27e7b35bcff..00000000000 --- a/server/sonar-web/src/main/js/components/ui/LegacyAvatar.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import * as React from 'react'; -import withAppStateContext from '../../app/components/app-state/withAppStateContext'; -import { AppState } from '../../types/appstate'; -import { GlobalSettingKeys } from '../../types/settings'; -import GenericAvatar from './GenericAvatar'; - -const GRAVATAR_SIZE_MULTIPLIER = 2; - -interface Props { - appState: AppState; - className?: string; - hash?: string; - name?: string; - size: number; -} - -/** - * @deprecated Use Avatar instead - */ -export function LegacyAvatar(props: Props) { - const { - appState: { settings }, - className, - hash, - name, - size, - } = props; - - const enableGravatar = settings[GlobalSettingKeys.EnableGravatar] === 'true'; - - if (!enableGravatar || !hash) { - if (!name) { - return null; - } - return <GenericAvatar className={className} name={name} size={size} />; - } - - const gravatarServerUrl = settings[GlobalSettingKeys.GravatarServerUrl] ?? ''; - const url = gravatarServerUrl - .replace('{EMAIL_MD5}', hash) - .replace('{SIZE}', String(size * GRAVATAR_SIZE_MULTIPLIER)); - - return ( - <img - alt={name} - className={classNames(className, 'rounded')} - height={size} - src={url} - width={size} - /> - ); -} - -export default withAppStateContext(LegacyAvatar); diff --git a/server/sonar-web/src/main/js/components/ui/Level.css b/server/sonar-web/src/main/js/components/ui/Level.css deleted file mode 100644 index 95b991e14b0..00000000000 --- a/server/sonar-web/src/main/js/components/ui/Level.css +++ /dev/null @@ -1,81 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.level { - display: inline-flex; - align-items: center; - justify-content: center; - width: auto; - min-width: 80px; - padding-left: 9px; - padding-right: 9px; - height: var(--controlHeight); - border-radius: var(--controlHeight); - box-sizing: border-box; - color: #fff; - letter-spacing: 0.02em; - font-size: var(--baseFontSize); - font-weight: 400; -} - -.level-small { - width: auto; - min-width: 64px; - padding-left: 9px; - padding-right: 9px; - margin-top: -1px; - margin-bottom: -1px; - height: var(--smallControlHeight); - font-size: var(--smallFontSize); - font-weight: bold; -} - -.level-muted { - background-color: var(--disabledQualityGate) !important; -} - -a > .level { - margin-bottom: -1px; - border-bottom: 1px solid; - transition: all 0.2s ease; -} - -a > .level:hover { - opacity: 0.8; -} - -.level-OK { - background-color: var(--success500); -} - -.level-WARN { - background-color: var(--orange); -} - -.level-ERROR { - background-color: var(--error700); -} - -.level-NONE { - background-color: var(--disabledQualityGate); -} - -.level-NOT_COMPUTED { - background-color: var(--disabledQualityGate); -} diff --git a/server/sonar-web/src/main/js/components/ui/Level.tsx b/server/sonar-web/src/main/js/components/ui/Level.tsx deleted file mode 100644 index fbf66cbee47..00000000000 --- a/server/sonar-web/src/main/js/components/ui/Level.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import * as React from 'react'; -import { formatMeasure } from '../../helpers/measures'; -import './Level.css'; - -export interface LevelProps { - 'aria-label'?: string; - 'aria-labelledby'?: string; - className?: string; - level: string; - small?: boolean; - muted?: boolean; -} - -export default function Level(props: LevelProps) { - const formatted = formatMeasure(props.level, 'LEVEL'); - const className = classNames(props.className, 'level', 'level-' + props.level, { - 'level-small': props.small, - 'level-muted': props.muted, - }); - - return ( - <span - aria-label={props['aria-label']} - aria-labelledby={props['aria-labelledby']} - className={className} - > - {formatted} - </span> - ); -} diff --git a/server/sonar-web/src/main/js/components/ui/NavBarTabs.css b/server/sonar-web/src/main/js/components/ui/NavBarTabs.css deleted file mode 100644 index a19b69e730f..00000000000 --- a/server/sonar-web/src/main/js/components/ui/NavBarTabs.css +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.navbar-tabs { - display: flex; - align-items: center; - clear: left; - height: var(--controlHeight); - margin-top: var(--gridSize); -} - -.navbar-tabs > li + li { - margin-left: 20px; -} - -.navbar-tabs > li > a, -.navbar-tabs > li > .button-link { - display: block; - height: var(--controlHeight); - line-height: 16px; - padding-top: 2px; - border-bottom: 3px solid transparent; - box-sizing: border-box; - color: var(--baseFontColor); - transition: none; -} - -.navbar-tabs > li > a.active, -.navbar-tabs > li > a:hover, -.navbar-tabs > li > a:focus, -.navbar-tabs > li > .button-link.active, -.navbar-tabs > li > .button-link:hover, -.navbar-tabs > li > .button-link:focus { - border-bottom-color: var(--blue); -} diff --git a/server/sonar-web/src/main/js/components/ui/NavBarTabs.tsx b/server/sonar-web/src/main/js/components/ui/NavBarTabs.tsx deleted file mode 100644 index 406636c2e83..00000000000 --- a/server/sonar-web/src/main/js/components/ui/NavBarTabs.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import * as React from 'react'; -import './NavBarTabs.css'; - -interface Props extends React.HTMLAttributes<HTMLUListElement> { - children?: React.ReactNode; - className?: string; -} - -export default function NavBarTabs({ children, className, ...other }: Props) { - return ( - <ul {...other} className={classNames('it__navbar-tabs navbar-tabs', className)}> - {children} - </ul> - ); -} diff --git a/server/sonar-web/src/main/js/components/ui/PageShortcutsTooltip.tsx b/server/sonar-web/src/main/js/components/ui/PageShortcutsTooltip.tsx deleted file mode 100644 index 7b6af58d0b3..00000000000 --- a/server/sonar-web/src/main/js/components/ui/PageShortcutsTooltip.tsx +++ /dev/null @@ -1,113 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import * as React from 'react'; -import Tooltip from '../../components/controls/Tooltip'; -import { translate, translateWithParameters } from '../../helpers/l10n'; - -export interface PageShortcutsTooltipProps { - className?: string; - leftAndRightLabel?: string; - leftLabel?: string; - upAndDownLabel?: string; - metaModifierLabel?: string; -} - -export default function PageShortcutsTooltip(props: PageShortcutsTooltipProps) { - const { className, leftAndRightLabel, leftLabel, upAndDownLabel, metaModifierLabel } = props; - return ( - <Tooltip - overlay={ - <div className="small nowrap"> - <div> - {upAndDownLabel && ( - <span> - <span className="shortcut-button little-spacer-right">↑</span> - <span className="shortcut-button spacer-right">↓</span> - {upAndDownLabel} - </span> - )} - {leftAndRightLabel && ( - <span className={classNames({ 'big-spacer-left': upAndDownLabel })}> - <span className="shortcut-button little-spacer-right">←</span> - <span className="shortcut-button spacer-right">→</span> - {leftAndRightLabel} - </span> - )} - {leftLabel && ( - <span className={classNames({ 'big-spacer-left': upAndDownLabel })}> - <span className="shortcut-button spacer-right">←</span> - {leftLabel} - </span> - )} - </div> - {metaModifierLabel && ( - <div className="big-spacer-top big-padded-top bordered-top"> - <span className="shortcut-button little-spacer-right">alt</span> - <span className="little-spacer-right">+</span> - <span className="shortcut-button little-spacer-right">↑</span> - <span className="shortcut-button spacer-right">↓</span> - <span className="shortcut-button little-spacer-right">←</span> - <span className="shortcut-button spacer-right">→</span> - {metaModifierLabel} - </div> - )} - </div> - } - > - <aside - aria-label={` - ${translate('shortcuts.on_page.intro')} - ${ - upAndDownLabel - ? translateWithParameters('shortcuts.on_page.up_down_x', upAndDownLabel) - : '' - } - ${ - leftAndRightLabel - ? translateWithParameters('shortcuts.on_page.left_right_x', leftAndRightLabel) - : '' - } - ${leftLabel ? translateWithParameters('shortcuts.on_page.left_x', leftLabel) : ''} - ${ - metaModifierLabel - ? translateWithParameters('shortcuts.on_page.meta_x', metaModifierLabel) - : '' - } - `} - className={classNames( - className, - 'page-shortcuts-tooltip note text-center display-inline-block', - )} - > - <div aria-hidden> - <div> - <span className="shortcut-button shortcut-button-tiny">↑</span> - </div> - <div> - <span className="shortcut-button shortcut-button-tiny">←</span> - <span className="shortcut-button shortcut-button-tiny">↓</span> - <span className="shortcut-button shortcut-button-tiny">→</span> - </div> - </div> - </aside> - </Tooltip> - ); -} diff --git a/server/sonar-web/src/main/js/components/ui/Spinner.css b/server/sonar-web/src/main/js/components/ui/Spinner.css deleted file mode 100644 index ad0a6e8b477..00000000000 --- a/server/sonar-web/src/main/js/components/ui/Spinner.css +++ /dev/null @@ -1,78 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.spinner { - position: relative; - vertical-align: middle; - width: 16px; - height: 16px; - border: 2px solid var(--blue); - border-radius: 50%; - animation: spin 0.75s infinite linear; -} - -.spinner:before, -.spinner:after { - left: -2px; - top: -2px; - display: none; - position: absolute; - content: ''; - width: inherit; - height: inherit; - border: inherit; - border-radius: inherit; -} - -.spinner, -.spinner:before, -.spinner:after { - display: inline-block; - box-sizing: border-box; - border-color: transparent; - border-top-color: var(--info500); - animation-duration: 1.2s; -} - -.spinner:before { - transform: rotate(120deg); -} - -.spinner:after { - transform: rotate(240deg); -} - -.spinner-placeholder { - position: relative; - display: inline-block; - vertical-align: middle; - width: 16px; - height: 16px; - visibility: hidden; -} - -@keyframes spin { - from { - transform: rotate(0deg); - } - - to { - transform: rotate(360deg); - } -} diff --git a/server/sonar-web/src/main/js/components/ui/Spinner.tsx b/server/sonar-web/src/main/js/components/ui/Spinner.tsx deleted file mode 100644 index 24b0ea25f57..00000000000 --- a/server/sonar-web/src/main/js/components/ui/Spinner.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import classNames from 'classnames'; -import * as React from 'react'; -import { translate } from '../../helpers/l10n'; -import './Spinner.css'; - -interface Props { - ariaLabel?: string; - children?: React.ReactNode; - className?: string; - customSpinner?: JSX.Element; - loading?: boolean; -} - -export default function Spinner(props: Props) { - const { - ariaLabel = translate('loading'), - children, - className, - customSpinner, - loading = true, - } = props; - - if (customSpinner) { - return <>{loading ? customSpinner : children}</>; - } - - return ( - <> - <div className="sw-overflow-hidden sw-relative"> - <i - aria-live="polite" - data-testid="spinner" - className={classNames('spinner', className, { - 'sw-sr-only sw-left-[-10000px]': !loading, - 'is-loading': loading, - })} - > - {loading && <span className="sw-sr-only">{ariaLabel}</span>} - </i> - </div> - {!loading && children} - </> - ); -} diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/PageShortcutsTooltip-test.tsx b/server/sonar-web/src/main/js/components/ui/__tests__/PageShortcutsTooltip-test.tsx deleted file mode 100644 index 610b40d59c5..00000000000 --- a/server/sonar-web/src/main/js/components/ui/__tests__/PageShortcutsTooltip-test.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import * as React from 'react'; -import { renderComponent } from '../../../helpers/testReactTestingUtils'; -import PageShortcutsTooltip, { PageShortcutsTooltipProps } from '../PageShortcutsTooltip'; - -const leftAndRightLabel = 'left & right'; -const leftLabel = 'left'; -const upAndDownLabel = 'up & down'; -const metaModifierLabel = 'meta'; - -it('should render all the labels', async () => { - const user = userEvent.setup(); - - renderPageShortcutsTooltip({ - leftAndRightLabel, - leftLabel, - upAndDownLabel, - metaModifierLabel, - }); - - await user.hover( - screen.getByLabelText( - 'shortcuts.on_page.intro shortcuts.on_page.up_down_x.up & down shortcuts.on_page.left_right_x.left & right shortcuts.on_page.left_x.left shortcuts.on_page.meta_x.meta', - ), - ); - - expect(await screen.findByText(leftAndRightLabel)).toBeInTheDocument(); - expect(screen.getByText(leftLabel)).toBeInTheDocument(); - expect(screen.getByText(upAndDownLabel)).toBeInTheDocument(); - expect(screen.getByText(metaModifierLabel)).toBeInTheDocument(); -}); - -it('should render left & right labels without up&down', async () => { - const user = userEvent.setup(); - - renderPageShortcutsTooltip({ - leftAndRightLabel, - leftLabel, - }); - - await user.hover( - screen.getByLabelText( - 'shortcuts.on_page.intro shortcuts.on_page.left_right_x.left & right shortcuts.on_page.left_x.left', - ), - ); - - expect(await screen.findByText(leftAndRightLabel)).toBeInTheDocument(); - expect(screen.getByText(leftLabel)).toBeInTheDocument(); -}); - -function renderPageShortcutsTooltip(props: Partial<PageShortcutsTooltipProps> = {}) { - return renderComponent(<PageShortcutsTooltip {...props} />); -} diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/Spinner-test.tsx b/server/sonar-web/src/main/js/components/ui/__tests__/Spinner-test.tsx deleted file mode 100644 index c9d321d94bf..00000000000 --- a/server/sonar-web/src/main/js/components/ui/__tests__/Spinner-test.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { render, screen } from '@testing-library/react'; -import * as React from 'react'; -import Spinner from '../Spinner'; - -it('can be controlled by the loading prop', () => { - const { rerender } = renderSpinner({ loading: true }); - expect(screen.getByText('loading')).toBeInTheDocument(); - - rerender(prepareSpinner({ loading: false })); - expect(screen.queryByText('loading')).not.toBeInTheDocument(); -}); - -function renderSpinner(props: Partial<Parameters<typeof Spinner>[0]> = {}) { - // We don't use our renderComponent() helper here, as we have some tests that - // require changes in props. - return render(prepareSpinner(props)); -} - -function prepareSpinner(props: Partial<Parameters<typeof Spinner>[0]> = {}) { - return <Spinner ariaLabel="loading" {...props} />; -} diff --git a/server/sonar-web/src/main/js/components/ui/popups.tsx b/server/sonar-web/src/main/js/components/ui/popups.tsx index 3932682648c..ad732d3dd99 100644 --- a/server/sonar-web/src/main/js/components/ui/popups.tsx +++ b/server/sonar-web/src/main/js/components/ui/popups.tsx @@ -20,11 +20,8 @@ /* eslint-disable prefer-destructuring */ import classNames from 'classnames'; -import { throttle } from 'lodash'; import * as React from 'react'; -import { createPortal, findDOMNode } from 'react-dom'; import ClickEventBoundary from '../controls/ClickEventBoundary'; -import ScreenPositionFixer from '../controls/ScreenPositionFixer'; import './popups.css'; /** @@ -81,216 +78,14 @@ function PopupBase(props: PopupProps, ref: React.Ref<HTMLDivElement>) { return inner; } -const PopupWithRef = React.forwardRef(PopupBase); -PopupWithRef.displayName = 'Popup'; - -export const Popup = PopupWithRef; - interface PopupArrowProps { style?: React.CSSProperties; } -export function PopupArrow(props: PopupArrowProps) { +function PopupArrow(props: PopupArrowProps) { return <div className="popup-arrow" style={props.style} />; } -interface PortalPopupProps extends Omit<PopupProps, 'arrowStyle' | 'style'> { - arrowOffset?: number; - children: React.ReactNode; - overlay: React.ReactNode; -} - -interface Measurements { - height: number; - left: number; - top: number; - width: number; -} - -type State = Partial<Measurements>; - -function isMeasured(state: State): state is Measurements { - return state.height !== undefined; -} - -export class PortalPopup extends React.Component<PortalPopupProps, State> { - mounted = false; - popupNode = React.createRef<HTMLDivElement>(); - throttledPositionTooltip: () => void; - - constructor(props: PortalPopupProps) { - super(props); - this.state = {}; - this.throttledPositionTooltip = throttle(this.positionPopup, 10); - } - - componentDidMount() { - this.mounted = true; - this.positionPopup(); - this.addEventListeners(); - } - - componentDidUpdate(prevProps: PortalPopupProps) { - if (this.props.placement !== prevProps.placement || this.props.overlay !== prevProps.overlay) { - this.positionPopup(); - } - } - - componentWillUnmount() { - this.mounted = false; - this.removeEventListeners(); - } - - addEventListeners = () => { - window.addEventListener('resize', this.throttledPositionTooltip); - window.addEventListener('scroll', this.throttledPositionTooltip); - }; - - removeEventListeners = () => { - window.removeEventListener('resize', this.throttledPositionTooltip); - window.removeEventListener('scroll', this.throttledPositionTooltip); - }; - - getPlacement = (): PopupPlacement => { - return this.props.placement || PopupPlacement.Bottom; - }; - - adjustArrowPosition = ( - placement: PopupPlacement, - { leftFix, topFix }: { leftFix: number; topFix: number }, - ) => { - const { arrowOffset = 0 } = this.props; - switch (placement) { - case PopupPlacement.Bottom: - case PopupPlacement.BottomLeft: - case PopupPlacement.BottomRight: - case PopupPlacement.TopLeft: - return { marginLeft: -leftFix + arrowOffset }; - default: - return { marginTop: -topFix + arrowOffset }; - } - }; - - positionPopup = () => { - // `findDOMNode(this)` will search for the DOM node for the current component - // first it will find a React.Fragment (see `render`), - // so it will get the DOM node of the first child, i.e. DOM node of `this.props.children` - // docs: https://reactjs.org/docs/refs-and-the-dom.html#exposing-dom-refs-to-parent-components - - // eslint-disable-next-line react/no-find-dom-node - const toggleNode = findDOMNode(this); - - if (toggleNode && toggleNode instanceof Element && this.popupNode.current) { - const toggleRect = toggleNode.getBoundingClientRect(); - const { width, height } = this.popupNode.current.getBoundingClientRect(); - let left = 0; - let top = 0; - - switch (this.getPlacement()) { - case PopupPlacement.Bottom: - left = toggleRect.left + toggleRect.width / 2 - width / 2; - top = toggleRect.top + toggleRect.height; - break; - case PopupPlacement.BottomLeft: - left = toggleRect.left; - top = toggleRect.top + toggleRect.height; - break; - case PopupPlacement.BottomRight: - left = toggleRect.left + toggleRect.width - width; - top = toggleRect.top + toggleRect.height; - break; - case PopupPlacement.LeftTop: - left = toggleRect.left - width; - top = toggleRect.top; - break; - case PopupPlacement.RightTop: - left = toggleRect.left + toggleRect.width; - top = toggleRect.top; - break; - case PopupPlacement.RightBottom: - left = toggleRect.left + toggleRect.width; - top = toggleRect.top + toggleRect.height - height; - break; - case PopupPlacement.TopLeft: - left = toggleRect.left; - top = toggleRect.top - height; - break; - } - - // save width and height (and later set in `render`) to avoid resizing the popup element, - // when it's placed close to the window edge - this.setState({ - left: window.pageXOffset + left, - top: window.pageYOffset + top, - width, - height, - }); - } - }; - - renderActual = ({ leftFix = 0, topFix = 0 }) => { - const { className, overlay, noPadding } = this.props; - const placement = this.getPlacement(); - let arrowStyle; - let style; - if (isMeasured(this.state)) { - style = { - left: this.state.left + leftFix, - top: this.state.top + topFix, - width: this.state.width, - height: this.state.height, - }; - arrowStyle = this.adjustArrowPosition(placement, { leftFix, topFix }); - } - - return ( - <Popup - arrowStyle={arrowStyle} - className={className} - noPadding={noPadding} - placement={placement} - ref={this.popupNode} - style={style} - > - {overlay} - </Popup> - ); - }; - - render() { - return ( - <> - {this.props.children} - {this.props.overlay && ( - <PortalWrapper> - <ScreenPositionFixer ready={isMeasured(this.state)}> - {this.renderActual} - </ScreenPositionFixer> - </PortalWrapper> - )} - </> - ); - } -} - -class PortalWrapper extends React.Component<React.PropsWithChildren> { - el: HTMLElement; - - constructor(props: {}) { - super(props); - this.el = document.createElement('div'); - this.el.classList.add('popup-portal'); - } - - componentDidMount() { - document.body.appendChild(this.el); - } - - componentWillUnmount() { - document.body.removeChild(this.el); - } - - render() { - return createPortal(this.props.children, this.el); - } -} +const PopupWithRef = React.forwardRef(PopupBase); +PopupWithRef.displayName = 'Popup'; +export const Popup = PopupWithRef; diff --git a/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx index b028aa6c3c6..211a546eb5c 100644 --- a/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx +++ b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx @@ -17,10 +17,10 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { ButtonSecondary } from 'design-system'; import * as React from 'react'; import { translate } from '../../helpers/l10n'; import { SystemUpgrade } from '../../types/system'; -import { Button } from '../controls/buttons'; import SystemUpgradeForm from './SystemUpgradeForm'; import { groupUpgrades, sortUpgrades, UpdateUseCase } from './utils'; @@ -45,9 +45,9 @@ export default function SystemUpgradeButton(props: Readonly<Props>) { return ( <> - <Button className="sw-ml-2" onClick={openSystemUpgradeForm}> + <ButtonSecondary className="sw-ml-2" onClick={openSystemUpgradeForm}> {translate('learn_more')} - </Button> + </ButtonSecondary> {isSystemUpgradeFormOpen && ( <SystemUpgradeForm onClose={closeSystemUpgradeForm} diff --git a/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx index e09e96cd8d7..7d65a0dc3aa 100644 --- a/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx +++ b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeForm.tsx @@ -17,16 +17,13 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { FlagMessage, Link, Modal, Variant } from 'design-system'; import { filter, flatMap, isEmpty, negate } from 'lodash'; import * as React from 'react'; import withAppStateContext from '../../app/components/app-state/withAppStateContext'; import { translate } from '../../helpers/l10n'; import { AppState } from '../../types/appstate'; import { SystemUpgrade } from '../../types/system'; -import Link from '../common/Link'; -import Modal from '../controls/Modal'; -import { ResetButtonLink } from '../controls/buttons'; -import { Alert, AlertVariant } from '../ui/Alert'; import SystemUpgradeItem from './SystemUpgradeItem'; import { SYSTEM_VERSION_REGEXP, UpdateUseCase } from './utils'; @@ -38,7 +35,7 @@ interface Props { updateUseCase?: UpdateUseCase; } -const MAP_ALERT: { [key in UpdateUseCase]?: AlertVariant } = { +const MAP_ALERT: { [key in UpdateUseCase]?: Variant } = { [UpdateUseCase.NewPatch]: 'warning', [UpdateUseCase.PreLTS]: 'warning', [UpdateUseCase.PreviousLTS]: 'error', @@ -79,38 +76,34 @@ export function SystemUpgradeForm(props: Readonly<Props>) { } return ( - <Modal contentLabel={header} onRequestClose={onClose}> - <div className="modal-head"> - <h2>{header}</h2> - </div> - - <div className="modal-body"> - {alertVariant && updateUseCase && ( - <Alert variant={alertVariant} className={`it__upgrade-alert-${updateUseCase}`}> - {translate('admin_notification.update', updateUseCase)} - </Alert> - )} - {systemUpgradesWithPatch.map((upgrades) => ( - <SystemUpgradeItem - edition={appState.edition} - key={upgrades[upgrades.length - 1].version} - systemUpgrades={upgrades} - isPatch={upgrades === patches} - isLTSVersion={upgrades.some((upgrade) => upgrade.version.startsWith(latestLTS))} - /> - ))} - </div> - <div className="modal-foot"> - <Link - className="pull-left link-no-underline display-flex-center" - to="https://www.sonarsource.com/products/sonarqube/downloads/?referrer=sonarqube" - target="_blank" - > + <Modal + headerTitle={header} + onClose={onClose} + body={ + <> + {alertVariant && updateUseCase && ( + <FlagMessage variant={alertVariant} className={`it__upgrade-alert-${updateUseCase}`}> + {translate('admin_notification.update', updateUseCase)} + </FlagMessage> + )} + {systemUpgradesWithPatch.map((upgrades) => ( + <SystemUpgradeItem + edition={appState.edition} + key={upgrades[upgrades.length - 1].version} + systemUpgrades={upgrades} + isPatch={upgrades === patches} + isLTSVersion={upgrades.some((upgrade) => upgrade.version.startsWith(latestLTS))} + /> + ))} + </> + } + primaryButton={ + <Link to="https://www.sonarsource.com/products/sonarqube/downloads/?referrer=sonarqube"> {translate('system.see_sonarqube_downloads')} </Link> - <ResetButtonLink onClick={onClose}>{translate('cancel')}</ResetButtonLink> - </div> - </Modal> + } + secondaryButtonLabel={translate('cancel')} + /> ); } diff --git a/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeIntermediate.tsx b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeIntermediate.tsx index 97ccc4c344b..47c53228c02 100644 --- a/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeIntermediate.tsx +++ b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeIntermediate.tsx @@ -17,11 +17,10 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { Accordion, BasicSeparator, Link, Note } from 'design-system'; import * as React from 'react'; import { translate } from '../../helpers/l10n'; import { SystemUpgrade } from '../../types/system'; -import { ButtonLink } from '../controls/buttons'; -import DropdownIcon from '../icons/DropdownIcon'; import DateFormatter from '../intl/DateFormatter'; interface Props { @@ -49,38 +48,38 @@ export default class SystemUpgradeIntermediate extends React.PureComponent<Props return ( <div className={this.props.className}> - <ButtonLink className="little-spacer-bottom" onClick={this.toggleIntermediatVersions}> - {showMore - ? translate('system.hide_intermediate_versions') - : translate('system.show_intermediate_versions')} - <DropdownIcon className="little-spacer-left" turned={showMore} /> - </ButtonLink> - {showMore && - upgrades.map((upgrade) => ( - <div className="note system-upgrade-intermediate" key={upgrade.version}> + <Accordion + header={ + showMore + ? translate('system.hide_intermediate_versions') + : translate('system.show_intermediate_versions') + } + open={showMore} + onClick={this.toggleIntermediatVersions} + > + {upgrades.map((upgrade, index) => ( + <Note className="sw-block sw-mb-4" key={upgrade.version}> {upgrade.releaseDate && ( <DateFormatter date={upgrade.releaseDate} long> {(formattedDate) => ( <p> - <b className="little-spacer-right">SonarQube {upgrade.version}</b> + <b className="sw-mr-1">SonarQube {upgrade.version}</b> {formattedDate} {upgrade.changeLogUrl && ( - <a - className="spacer-left" - href={upgrade.changeLogUrl} - rel="noopener noreferrer" - target="_blank" - > + <Link className="sw-ml-2" to={upgrade.changeLogUrl}> {translate('system.release_notes')} - </a> + </Link> )} </p> )} </DateFormatter> )} - {upgrade.description && <p className="little-spacer-top">{upgrade.description}</p>} - </div> + {upgrade.description && <p className="sw-mt-2">{upgrade.description}</p>} + + {index !== upgrades.length - 1 && <BasicSeparator />} + </Note> ))} + </Accordion> </div> ); } diff --git a/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeItem.tsx b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeItem.tsx index 89294461b1e..5fbb2353f22 100644 --- a/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeItem.tsx +++ b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeItem.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { DownloadButton, Link, SubHeading } from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { @@ -27,8 +28,7 @@ import { import { translate, translateWithParameters } from '../../helpers/l10n'; import { EditionKey } from '../../types/editions'; import { SystemUpgrade } from '../../types/system'; -import DocLink from '../common/DocLink'; -import Link from '../common/Link'; +import DocumentationLink from '../common/DocumentationLink'; import DateFormatter from '../intl/DateFormatter'; import SystemUpgradeIntermediate from './SystemUpgradeIntermediate'; @@ -55,18 +55,17 @@ export default function SystemUpgradeItem(props: SystemUpgradeItemProps) { return ( <div className="system-upgrade-version it__upgrade-list-item"> - <h3 className="h1 spacer-bottom"> + <SubHeading as="h3"> <strong>{header}</strong> {!isPatch && ( <Link - className="spacer-left medium" + className="sw-ml-2" to="https://www.sonarsource.com/products/sonarqube/whats-new/?referrer=sonarqube" - target="_blank" > {translate('system.see_whats_new')} </Link> )} - </h3> + </SubHeading> <p> <FormattedMessage defaultMessage={translate('system.version_is_availble')} @@ -74,8 +73,8 @@ export default function SystemUpgradeItem(props: SystemUpgradeItemProps) { values={{ version: <b>SonarQube {lastUpgrade.version}</b> }} /> </p> - <p className="spacer-top">{lastUpgrade.description}</p> - <div className="big-spacer-top"> + <p className="sw-mt-2">{lastUpgrade.description}</p> + <div className="sw-mt-4"> {lastUpgrade.releaseDate && ( <DateFormatter date={lastUpgrade.releaseDate} long> {(formattedDate) => ( @@ -84,25 +83,23 @@ export default function SystemUpgradeItem(props: SystemUpgradeItemProps) { </DateFormatter> )} {lastUpgrade.changeLogUrl && ( - <Link className="spacer-left" to={lastUpgrade.changeLogUrl} target="_blank"> + <Link className="sw-ml-2" to={lastUpgrade.changeLogUrl}> {translate('system.release_notes')} </Link> )} </div> - <SystemUpgradeIntermediate className="spacer-top" upgrades={systemUpgrades.slice(1)} /> - <div className="big-spacer-top"> - <a - className="button" - download={getEditionDownloadFilename(downloadUrl)} - href={downloadUrl} - rel="noopener noreferrer" - target="_blank" - > + <SystemUpgradeIntermediate className="sw-mt-2" upgrades={systemUpgrades.slice(1)} /> + <div className="sw-mt-4"> + <DownloadButton download={getEditionDownloadFilename(downloadUrl)} href={downloadUrl}> {translateWithParameters('system.download_x', lastUpgrade.version)} - </a> - <DocLink className="spacer-left" to="/setup-and-upgrade/upgrade-the-server/upgrade-guide/"> + </DownloadButton> + + <DocumentationLink + className="sw-ml-2" + to="/setup-and-upgrade/upgrade-the-server/upgrade-guide/" + > {translate('system.how_to_upgrade')} - </DocLink> + </DocumentationLink> </div> </div> ); diff --git a/server/sonar-web/src/main/js/helpers/search.tsx b/server/sonar-web/src/main/js/helpers/search.tsx index b1bc97a005d..9dff03c7635 100644 --- a/server/sonar-web/src/main/js/helpers/search.tsx +++ b/server/sonar-web/src/main/js/helpers/search.tsx @@ -31,3 +31,8 @@ export function highlightTerm(str: string, term: string) { str ); } + +export interface LabelValueSelectOption<V = string> { + label: string; + value: V; +} diff --git a/server/sonar-web/src/main/js/types/extension.ts b/server/sonar-web/src/main/js/types/extension.ts index f9ce8d25fc0..b6f5d72de02 100644 --- a/server/sonar-web/src/main/js/types/extension.ts +++ b/server/sonar-web/src/main/js/types/extension.ts @@ -17,13 +17,15 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +import { Theme } from '@emotion/react'; import { QueryClient } from '@tanstack/react-query'; -import { Theme } from 'design-system'; import { IntlShape } from 'react-intl'; import { Location, Router } from '../components/hoc/withRouter'; import { AppState } from './appstate'; +import { BranchLike } from './branch-like'; import { L10nBundle } from './l10nBundle'; -import { Component, Dict } from './types'; +import { Component } from './types'; import { CurrentUser, HomePage } from './users'; export enum AdminPageExtension { @@ -31,39 +33,36 @@ export enum AdminPageExtension { } export interface ExtensionRegistryEntry { - start: ExtensionStartMethod; providesCSSFile: boolean; + start: ExtensionStartMethod; } -export interface ExtensionStartMethod { - (params: ExtensionStartMethodParameter | string): ExtensionStartMethodReturnType; +export type ExtensionStartMethod = ( + params: ExtensionStartMethodParameter | string, +) => ExtensionStartMethodReturnType; + +export interface ExtensionOptions { + branchLike?: BranchLike; + component: Component; + intl: IntlShape; + l10nBundle: L10nBundle; + location: Location; + router: Router; + theme: Theme; } -export interface ExtensionStartMethodParameter { +export interface ExtensionStartMethodParameter extends Omit<ExtensionOptions, 'component'> { appState: AppState; - el: HTMLElement | undefined | null; + baseUrl: string; component?: Component; + currentUser: CurrentUser; + el: HTMLElement | undefined | null; onBranchesChange?: (updateBranches?: boolean, updatePRs?: boolean) => void; onComponentChange?: (changes: Partial<Component>) => void; - currentUser: CurrentUser; - intl: IntlShape; - location: Location; - router: Router; - theme: { - colors: Dict<string>; - sizes: Dict<string>; - rawSizes: Dict<number>; - fonts: Dict<string>; - zIndexes: Dict<string>; - others: Dict<string>; - }; - dsTheme: Theme; - baseUrl: string; - l10nBundle: L10nBundle; queryClient: QueryClient; // See SONAR-16207 and core-extension-enterprise-server/src/main/js/portfolios/components/Header.tsx // for more information on why we're passing this as a prop to an extension. updateCurrentUserHomepage: (homepage: HomePage) => void; } -export type ExtensionStartMethodReturnType = React.ReactNode | Function | void | undefined | null; +export type ExtensionStartMethodReturnType = React.ReactNode | Function | void; diff --git a/server/sonar-web/tailwind.base.config.js b/server/sonar-web/tailwind.base.config.js index 3352639715a..8f73e0c950b 100644 --- a/server/sonar-web/tailwind.base.config.js +++ b/server/sonar-web/tailwind.base.config.js @@ -29,6 +29,7 @@ module.exports = { auto: 'auto', default: 'default', pointer: 'pointer', + text: 'text', 'not-allowed': 'not-allowed', }, // Define font sizes diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 38c11900b09..fd3927826d9 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -4053,6 +4053,7 @@ overview.activity.variations.first_analysis=First analysis: # WS API # #------------------------------------------------------------------------------ +api_documentation.domain_nav=Web API domains navigation api_documentation.deprecation_tooltip=An API deprecated in version X.Y will be dropped in version (X+1).0. Example: an API deprecated in 4.1 is supported in 4.X (4.2, 4.3, etc.) and will be dropped in version 5.0. api_documentation.internal_tooltip=Use at your own risk; internal services are subject to change or removal without notice. api_documentation.internal_tooltip_v2=Use at your own risk. Shows/hides the internal endpoints, parameters, and other details used for internal services. These are subject to change or removal without notice. @@ -5160,7 +5161,7 @@ indexation.link_unavailable=The link to these results is unavailable until this indexation.features_partly_available=Most features are available. Some details only show upon completion. {link} indexation.features_partly_available.link=More info indexation.progression={0} out of {1} projects reindexed. -indexation.progression_with_error={0} out of {1} projects reindexed with some {link}. +indexation.progression_with_error={count} out of {total} projects reindexed with some {link}. indexation.progression_with_error.link=tasks failing indexation.completed=All project data has been reloaded. indexation.completed_with_error=SonarQube completed the reload of project data. Some {link} causing some projects to remain unavailable. |