diff options
author | stanislavh <stanislav.honcharov@sonarsource.com> | 2023-05-16 18:27:46 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-05-24 20:03:14 +0000 |
commit | 20c81961de9c92422410a5657ed48a3093742bde (patch) | |
tree | ea6e6ec3f3d5eb88b1d26cfd5ab4e742821c6ba1 /server/sonar-web/design-system/src/components | |
parent | 28a8a0f6d2e079e16a511d4112dbf17e66c514f7 (diff) | |
download | sonarqube-20c81961de9c92422410a5657ed48a3093742bde.tar.gz sonarqube-20c81961de9c92422410a5657ed48a3093742bde.zip |
SONAR-19236 Implement new design for hotspot header
Diffstat (limited to 'server/sonar-web/design-system/src/components')
9 files changed, 184 insertions, 3 deletions
diff --git a/server/sonar-web/design-system/src/components/FormField.tsx b/server/sonar-web/design-system/src/components/FormField.tsx new file mode 100644 index 00000000000..a6bac58d5e8 --- /dev/null +++ b/server/sonar-web/design-system/src/components/FormField.tsx @@ -0,0 +1,74 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { ReactNode } from 'react'; +import tw from 'twin.macro'; +import { Highlight, Note } from './Text'; +import { RequiredIcon } from './icons'; + +interface Props { + ariaLabel?: string; + children: ReactNode; + className?: string; + description?: string | ReactNode; + help?: ReactNode; + htmlFor?: string; + id?: string; + label: string | ReactNode; + required?: boolean; + title?: string; +} + +export function FormField({ + children, + className, + description, + help, + id, + required, + label, + htmlFor, + title, + ariaLabel, +}: Props) { + return ( + <FieldWrapper className={className} id={id}> + <label aria-label={ariaLabel} className="sw-mb-2" htmlFor={htmlFor} title={title}> + <Highlight className="sw-flex sw-items-center sw-gap-2"> + {label} + {required && <RequiredIcon className="sw--ml-1" />} + {help} + </Highlight> + </label> + + {children} + + {description && <Note className="sw-mt-2">{description}</Note>} + </FieldWrapper> + ); +} + +const FieldWrapper = styled.div` + ${tw`sw-flex sw-flex-col sw-w-full`} + + &:not(:last-of-type) { + ${tw`sw-mb-6`} + } +`; diff --git a/server/sonar-web/design-system/src/components/SearchSelectDropdown.tsx b/server/sonar-web/design-system/src/components/SearchSelectDropdown.tsx index 67987e450bd..7493c146ea6 100644 --- a/server/sonar-web/design-system/src/components/SearchSelectDropdown.tsx +++ b/server/sonar-web/design-system/src/components/SearchSelectDropdown.tsx @@ -52,6 +52,7 @@ export interface SearchSelectDropdownProps< Group extends GroupBase<Option> = GroupBase<Option> > extends SelectProps<V, Option, IsMulti, Group>, AsyncProps<Option, IsMulti, Group> { + controlAriaLabel?: string; controlLabel?: React.ReactNode | string; isDiscreet?: boolean; } @@ -62,7 +63,16 @@ export function SearchSelectDropdown< IsMulti extends boolean = false, Group extends GroupBase<Option> = GroupBase<Option> >(props: SearchSelectDropdownProps<V, Option, IsMulti, Group>) { - const { isDiscreet, value, loadOptions, controlLabel, isDisabled, minLength, ...rest } = props; + const { + isDiscreet, + value, + loadOptions, + controlLabel, + isDisabled, + minLength, + controlAriaLabel, + ...rest + } = props; const [open, setOpen] = React.useState(false); const [inputValue, setInputValue] = React.useState(''); @@ -112,6 +122,7 @@ export function SearchSelectDropdown< <DropdownToggler allowResizing={true} className="sw-overflow-visible sw-border-none" + isPortal={true} onRequestClose={() => { toggleDropdown(false); }} @@ -140,6 +151,7 @@ export function SearchSelectDropdown< } > <SearchSelectDropdownControl + ariaLabel={controlAriaLabel} disabled={isDisabled} isDiscreet={isDiscreet} label={controlLabel} diff --git a/server/sonar-web/design-system/src/components/SearchSelectDropdownControl.tsx b/server/sonar-web/design-system/src/components/SearchSelectDropdownControl.tsx index fcb802f32ff..e0b50db401c 100644 --- a/server/sonar-web/design-system/src/components/SearchSelectDropdownControl.tsx +++ b/server/sonar-web/design-system/src/components/SearchSelectDropdownControl.tsx @@ -27,6 +27,7 @@ import { InputSizeKeys } from '../types/theme'; import { ChevronDownIcon } from './icons'; interface SearchSelectDropdownControlProps { + ariaLabel?: string; disabled?: boolean; isDiscreet?: boolean; label?: React.ReactNode | string; @@ -35,9 +36,10 @@ interface SearchSelectDropdownControlProps { } export function SearchSelectDropdownControl(props: SearchSelectDropdownControlProps) { - const { disabled, label, isDiscreet, onClick, size = 'full' } = props; + const { disabled, label, isDiscreet, onClick, size = 'full', ariaLabel = '' } = props; return ( <StyledControl + aria-label={ariaLabel} className={classNames({ 'is-discreet': isDiscreet })} onClick={() => { if (!disabled) { diff --git a/server/sonar-web/design-system/src/components/Text.tsx b/server/sonar-web/design-system/src/components/Text.tsx index f5ac7df3a6e..aff69978ad3 100644 --- a/server/sonar-web/design-system/src/components/Text.tsx +++ b/server/sonar-web/design-system/src/components/Text.tsx @@ -105,3 +105,15 @@ export const LightPrimary = styled.span` export const PageContentFontWrapper = styled.div` color: ${themeColor('pageContent')}; `; + +export const Highlight = styled.strong` + color: ${themeColor('pageContentDark')}; + + ${tw`sw-body-sm-highlight`} +`; + +export const Note = styled.span` + color: ${themeColor('pageContentLight')}; + + ${tw`sw-body-sm`} +`; diff --git a/server/sonar-web/design-system/src/components/__tests__/FormField-test.tsx b/server/sonar-web/design-system/src/components/__tests__/FormField-test.tsx new file mode 100644 index 00000000000..ee86ad31e26 --- /dev/null +++ b/server/sonar-web/design-system/src/components/__tests__/FormField-test.tsx @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { FCProps } from '~types/misc'; +import { render } from '../../helpers/testUtils'; +import { FormField } from '../FormField'; + +it('should render correctly', () => { + renderFormField({}, <input id="input" />); + expect(screen.getByLabelText('Hello')).toBeInTheDocument(); +}); + +it('should render with required and description', () => { + renderFormField({ description: 'some description', required: true }, <input id="input" />); + expect(screen.getByText('some description')).toBeInTheDocument(); + expect(screen.getByText('*')).toBeInTheDocument(); +}); + +function renderFormField( + props: Partial<FCProps<typeof FormField>> = {}, + children: any = <div>Fake input</div> +) { + return render( + <FormField htmlFor="input" label="Hello" {...props}> + {children} + </FormField> + ); +} diff --git a/server/sonar-web/design-system/src/components/buttons.tsx b/server/sonar-web/design-system/src/components/buttons.tsx index 28650a777c0..c6d2ccd3ac8 100644 --- a/server/sonar-web/design-system/src/components/buttons.tsx +++ b/server/sonar-web/design-system/src/components/buttons.tsx @@ -38,7 +38,7 @@ export interface ButtonProps extends AllowedButtonAttributes { disabled?: boolean; icon?: React.ReactNode; innerRef?: React.Ref<HTMLButtonElement>; - onClick?: (event?: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => void; + onClick?: (event?: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => unknown; preventDefault?: boolean; reloadDocument?: LinkProps['reloadDocument']; diff --git a/server/sonar-web/design-system/src/components/icons/RequiredIcon.tsx b/server/sonar-web/design-system/src/components/icons/RequiredIcon.tsx new file mode 100644 index 00000000000..d39fbc6c197 --- /dev/null +++ b/server/sonar-web/design-system/src/components/icons/RequiredIcon.tsx @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 tw from 'twin.macro'; +import { themeColor } from '../../helpers/theme'; + +export function RequiredIcon(props: React.ComponentPropsWithoutRef<'em'>) { + return <StyledEm {...props}>*</StyledEm>; +} + +export const StyledEm = styled.em` + ${tw`sw-body-sm`} + ${tw`sw-not-italic`} + ${tw`sw-ml-2`} + color: ${themeColor('inputRequired')}; +`; diff --git a/server/sonar-web/design-system/src/components/icons/index.ts b/server/sonar-web/design-system/src/components/icons/index.ts index 6f4a7a4c893..301e4c08af8 100644 --- a/server/sonar-web/design-system/src/components/icons/index.ts +++ b/server/sonar-web/design-system/src/components/icons/index.ts @@ -53,6 +53,7 @@ export { PencilIcon } from './PencilIcon'; export { ProjectIcon } from './ProjectIcon'; export { PullRequestIcon } from './PullRequestIcon'; export { RefreshIcon } from './RefreshIcon'; +export { RequiredIcon } from './RequiredIcon'; export { SecurityHotspotIcon } from './SecurityHotspotIcon'; export { SeparatorCircleIcon } from './SeparatorCircleIcon'; export { SeverityBlockerIcon } from './SeverityBlockerIcon'; diff --git a/server/sonar-web/design-system/src/components/index.ts b/server/sonar-web/design-system/src/components/index.ts index edff1756a63..7e500593b08 100644 --- a/server/sonar-web/design-system/src/components/index.ts +++ b/server/sonar-web/design-system/src/components/index.ts @@ -39,6 +39,7 @@ export * from './FacetItem'; export { FailedQGConditionLink } from './FailedQGConditionLink'; export { FlagMessage } from './FlagMessage'; export * from './FlowStep'; +export * from './FormField'; export * from './GenericAvatar'; export * from './HighlightedSection'; export { HotspotRating } from './HotspotRating'; |