]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19246 Implement sub navigation in the new component library
author7PH <benjamin.raymond@sonarsource.com>
Tue, 9 May 2023 10:49:55 +0000 (12:49 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 10 May 2023 20:05:28 +0000 (20:05 +0000)
server/sonar-web/design-system/src/components/index.ts
server/sonar-web/design-system/src/components/subnavigation/SubnavigationAccordion.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/subnavigation/SubnavigationGroup.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/subnavigation/SubnavigationHeading.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/subnavigation/SubnavigationItem.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/subnavigation/__tests__/SubnavigationAccordion-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/subnavigation/__tests__/SubnavigationItem-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/subnavigation/index.ts [new file with mode: 0644]

index 553212a1502fcdf195f3a83f4315f779385861d8..9164936c0486a34b510320d4e17329681ed04a5b 100644 (file)
@@ -57,3 +57,4 @@ export * from './buttons';
 export * from './icons';
 export * from './layouts';
 export * from './popups';
+export * from './subnavigation';
diff --git a/server/sonar-web/design-system/src/components/subnavigation/SubnavigationAccordion.tsx b/server/sonar-web/design-system/src/components/subnavigation/SubnavigationAccordion.tsx
new file mode 100644 (file)
index 0000000..38c1d11
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * 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, useCallback, useState } from 'react';
+import tw from 'twin.macro';
+import { themeColor, themeContrast } from '../../helpers/theme';
+import { BareButton } from '../buttons';
+import { OpenCloseIndicator } from '../icons/OpenCloseIndicator';
+import { SubnavigationGroup } from './SubnavigationGroup';
+
+interface Props {
+  children: ReactNode;
+  className?: string;
+  header: ReactNode;
+  id: string;
+  initExpanded?: boolean;
+}
+
+export function SubnavigationAccordion(props: Props) {
+  const { children, className, header, id, initExpanded } = props;
+  const [expanded, setExpanded] = useState(initExpanded);
+  const toggleExpanded = useCallback(() => {
+    setExpanded((expanded) => !expanded);
+  }, [setExpanded]);
+
+  return (
+    <SubnavigationGroup
+      aria-labelledby={`${id}-subnavigation-accordion-button`}
+      className={className}
+      id={`${id}-subnavigation-accordion`}
+      role="region"
+    >
+      <SubnavigationAccordionItem
+        aria-controls={`${id}-subnavigation-accordion`}
+        aria-expanded={expanded}
+        id={`${id}-subnavigation-accordion-button`}
+        onClick={toggleExpanded}
+      >
+        {header}
+        <OpenCloseIndicator open={Boolean(expanded)} />
+      </SubnavigationAccordionItem>
+      {expanded && children}
+    </SubnavigationGroup>
+  );
+}
+
+const SubnavigationAccordionItem = styled(BareButton)`
+  ${tw`sw-flex sw-items-center sw-justify-between`}
+  ${tw`sw-box-border`}
+  ${tw`sw-body-sm-highlight`}
+  ${tw`sw-p-4`}
+  ${tw`sw-w-full`}
+  ${tw`sw-cursor-pointer`}
+
+  color: ${themeContrast('subnavigation')};
+  background-color: ${themeColor('subnavigation')};
+  transition: 0.2 ease;
+  transition-property: border-left, background-color, color;
+
+  &:hover,
+  &:focus {
+    background-color: ${themeColor('subnavigationHover')};
+  }
+`;
+SubnavigationAccordionItem.displayName = 'SubnavigationAccordionItem';
diff --git a/server/sonar-web/design-system/src/components/subnavigation/SubnavigationGroup.tsx b/server/sonar-web/design-system/src/components/subnavigation/SubnavigationGroup.tsx
new file mode 100644 (file)
index 0000000..c26670c
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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 { Children, Fragment, HtmlHTMLAttributes, ReactNode } from 'react';
+import tw from 'twin.macro';
+import { themeBorder, themeColor } from '../../helpers/theme';
+import { isDefined } from '../../helpers/types';
+
+interface Props extends HtmlHTMLAttributes<HTMLDivElement> {
+  children: ReactNode;
+  className?: string;
+}
+
+export function SubnavigationGroup({ className, children, ...htmlProps }: Props) {
+  const childrenArray = Children.toArray(children).filter(isDefined);
+  return (
+    <Group className={className} {...htmlProps}>
+      {childrenArray.map((child, index) => (
+        <Fragment key={index}>
+          {child}
+          {index < childrenArray.length - 1 && <Separator />}
+        </Fragment>
+      ))}
+    </Group>
+  );
+}
+
+const Group = styled.div`
+  ${tw`sw-relative`}
+  ${tw`sw-flex sw-flex-col`}
+  ${tw`sw-w-full`}
+
+  background-color: ${themeColor('subnavigation')};
+  border: ${themeBorder('default', 'subnavigationBorder')};
+`;
+
+const Separator = styled.div`
+  ${tw`sw-w-full`}
+
+  height: 1px;
+  background-color: ${themeColor('subnavigationSeparator')};
+`;
diff --git a/server/sonar-web/design-system/src/components/subnavigation/SubnavigationHeading.tsx b/server/sonar-web/design-system/src/components/subnavigation/SubnavigationHeading.tsx
new file mode 100644 (file)
index 0000000..5ae5f9f
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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, themeContrast } from '../../helpers/theme';
+
+export const SubnavigationHeading = styled.div`
+  ${tw`sw-flex sw-items-center sw-justify-between`}
+  ${tw`sw-box-border`}
+  ${tw`sw-body-sm`}
+  ${tw`sw-p-4`}
+  ${tw`sw-w-full`}
+
+  color: ${themeContrast('subnavigation')};
+  background-color: ${themeColor('subnavigation')};
+`;
+SubnavigationHeading.displayName = 'SubnavigationHeading';
diff --git a/server/sonar-web/design-system/src/components/subnavigation/SubnavigationItem.tsx b/server/sonar-web/design-system/src/components/subnavigation/SubnavigationItem.tsx
new file mode 100644 (file)
index 0000000..eade586
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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 classNames from 'classnames';
+import { ReactNode, useCallback } from 'react';
+import tw, { theme as twTheme } from 'twin.macro';
+import { themeBorder, themeColor, themeContrast } from '../../helpers/theme';
+import { BareButton } from '../buttons';
+
+interface Props {
+  active?: boolean;
+  children: ReactNode;
+  className?: string;
+  innerRef?: (node: HTMLButtonElement) => void;
+  onClick: (value?: string) => void;
+  value?: string;
+}
+
+export function SubnavigationItem(props: Props) {
+  const { active, className, children, innerRef, onClick, value } = props;
+  const handleClick = useCallback(() => {
+    onClick(value);
+  }, [onClick, value]);
+  return (
+    <SubnavigationItemStyled
+      aria-current={active}
+      className={classNames('js-subnavigation-item', { active }, className)}
+      onClick={handleClick}
+      ref={innerRef}
+    >
+      {children}
+    </SubnavigationItemStyled>
+  );
+}
+
+const SubnavigationItemStyled = styled(BareButton)`
+  ${tw`sw-flex sw-items-center sw-justify-between`}
+  ${tw`sw-box-border`}
+  ${tw`sw-body-sm`}
+  ${tw`sw-py-4 sw-pr-4`}
+  ${tw`sw-w-full`}
+  ${tw`sw-cursor-pointer`}
+
+  padding-left: calc(${twTheme('spacing.4')} - 3px);
+  color: ${themeContrast('subnavigation')};
+  background-color: ${themeColor('subnavigation')};
+  border-left: ${themeBorder('active', 'transparent')};
+  transition: 0.2 ease;
+  transition-property: border-left, background-color, color;
+
+  &:hover,
+  &:focus,
+  &.active {
+    background-color: ${themeColor('subnavigationHover')};
+  }
+
+  &.active {
+    color: ${themeContrast('subnavigationHover')};
+    border-left: ${themeBorder('active')};
+  }
+`;
diff --git a/server/sonar-web/design-system/src/components/subnavigation/__tests__/SubnavigationAccordion-test.tsx b/server/sonar-web/design-system/src/components/subnavigation/__tests__/SubnavigationAccordion-test.tsx
new file mode 100644 (file)
index 0000000..4491543
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * 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 userEvent from '@testing-library/user-event';
+import { render } from '../../../helpers/testUtils';
+import { FCProps } from '../../../types/misc';
+import { SubnavigationAccordion } from '../SubnavigationAccordion';
+
+it('should have correct style and html structure', () => {
+  setupWithProps();
+
+  expect(screen.getByRole('button', { expanded: false })).toBeVisible();
+  expect(screen.queryByText('Foo')).not.toBeInTheDocument();
+});
+
+it('should display expanded', () => {
+  setupWithProps({ initExpanded: true });
+
+  expect(screen.getByRole('button', { expanded: true })).toBeVisible();
+  expect(screen.getByText('Foo')).toBeVisible();
+});
+
+it('should toggle expand', async () => {
+  const user = userEvent.setup();
+  setupWithProps();
+
+  expect(screen.queryByText('Foo')).not.toBeInTheDocument();
+  await user.click(screen.getByRole('button'));
+  expect(screen.getByText('Foo')).toBeVisible();
+  await user.click(screen.getByRole('button'));
+  expect(screen.queryByText('Foo')).not.toBeInTheDocument();
+});
+
+function setupWithProps(props: Partial<FCProps<typeof SubnavigationAccordion>> = {}) {
+  return render(
+    <SubnavigationAccordion header="Header" id="test" initExpanded={false} {...props}>
+      <span>Foo</span>
+    </SubnavigationAccordion>
+  );
+}
diff --git a/server/sonar-web/design-system/src/components/subnavigation/__tests__/SubnavigationItem-test.tsx b/server/sonar-web/design-system/src/components/subnavigation/__tests__/SubnavigationItem-test.tsx
new file mode 100644 (file)
index 0000000..3646eee
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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 { render } from '../../../helpers/testUtils';
+import { FCProps } from '../../../types/misc';
+import { SubnavigationItem } from '../SubnavigationItem';
+
+it('should render correctly', () => {
+  setupWithProps();
+
+  expect(screen.getByRole('button', { current: false })).toBeVisible();
+});
+
+it('should display selected', () => {
+  setupWithProps({ active: true });
+
+  expect(screen.getByRole('button', { current: true })).toBeVisible();
+});
+
+it('should call onClick with value when clicked', async () => {
+  const onClick = jest.fn();
+  const { user } = setupWithProps({ onClick });
+
+  await user.click(screen.getByRole('button'));
+  expect(onClick).toHaveBeenCalledWith('foo');
+});
+
+function setupWithProps(props: Partial<FCProps<typeof SubnavigationItem>> = {}) {
+  return render(
+    <SubnavigationItem active={false} onClick={jest.fn()} value="foo" {...props}>
+      Foo
+    </SubnavigationItem>
+  );
+}
diff --git a/server/sonar-web/design-system/src/components/subnavigation/index.ts b/server/sonar-web/design-system/src/components/subnavigation/index.ts
new file mode 100644 (file)
index 0000000..89410cd
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+export * from './SubnavigationAccordion';
+export * from './SubnavigationGroup';
+export * from './SubnavigationHeading';
+export * from './SubnavigationItem';