diff --git a/front/src/modules/comments/components/comment-thread/CommentThreadCreateButton.tsx b/front/src/modules/comments/components/comment-thread/CommentThreadCreateButton.tsx
new file mode 100644
index 0000000000..8d13759685
--- /dev/null
+++ b/front/src/modules/comments/components/comment-thread/CommentThreadCreateButton.tsx
@@ -0,0 +1,40 @@
+import { useTheme } from '@emotion/react';
+
+import { Button } from '@/ui/components/buttons/Button';
+import { ButtonGroup } from '@/ui/components/buttons/ButtonGroup';
+import { IconCheckbox, IconNotes, IconTimelineEvent } from '@/ui/icons/index';
+
+type CommentThreadCreateButtonProps = {
+ onNoteClick?: () => void;
+ onTaskClick?: () => void;
+ onActivityClick?: () => void;
+};
+
+export function CommentThreadCreateButton({
+ onNoteClick,
+ onTaskClick,
+ onActivityClick,
+}: CommentThreadCreateButtonProps) {
+ const theme = useTheme();
+ return (
+
+ }
+ title="Note"
+ onClick={onNoteClick}
+ />
+ }
+ title="Task"
+ soon={true}
+ onClick={onTaskClick}
+ />
+ }
+ title="Activity"
+ soon={true}
+ onClick={onActivityClick}
+ />
+
+ );
+}
diff --git a/front/src/modules/comments/components/timeline/Timeline.tsx b/front/src/modules/comments/components/timeline/Timeline.tsx
index 5c3c2301f6..24fea7d79e 100644
--- a/front/src/modules/comments/components/timeline/Timeline.tsx
+++ b/front/src/modules/comments/components/timeline/Timeline.tsx
@@ -6,7 +6,6 @@ import { useOpenCommentThreadRightDrawer } from '@/comments/hooks/useOpenComment
import { useOpenCreateCommentThreadDrawer } from '@/comments/hooks/useOpenCreateCommentThreadDrawer';
import { CommentableEntity } from '@/comments/types/CommentableEntity';
import { CommentThreadForDrawer } from '@/comments/types/CommentThreadForDrawer';
-import { TableActionBarButtonToggleComments } from '@/ui/components/table/action-bar/TableActionBarButtonOpenComments';
import { IconCirclePlus, IconNotes } from '@/ui/icons/index';
import {
beautifyExactDate,
@@ -17,6 +16,8 @@ import {
useGetCommentThreadsByTargetsQuery,
} from '~/generated/graphql';
+import { CommentThreadCreateButton } from '../comment-thread/CommentThreadCreateButton';
+
const StyledMainContainer = styled.div`
align-items: flex-start;
align-self: stretch;
@@ -208,8 +209,8 @@ export function Timeline({ entity }: { entity: CommentableEntity }) {
No activity yet
Create one:
- openCreateCommandThread(entity)}
+ openCreateCommandThread(entity)}
/>
);
@@ -223,8 +224,8 @@ export function Timeline({ entity }: { entity: CommentableEntity }) {
- openCreateCommandThread(entity)}
+ openCreateCommandThread(entity)}
/>
diff --git a/front/src/modules/ui/components/accessories/SoonPill.tsx b/front/src/modules/ui/components/accessories/SoonPill.tsx
new file mode 100644
index 0000000000..3b64a245f2
--- /dev/null
+++ b/front/src/modules/ui/components/accessories/SoonPill.tsx
@@ -0,0 +1,22 @@
+import styled from '@emotion/styled';
+
+const StyledSoonPill = styled.span`
+ align-items: center;
+ background: ${({ theme }) => theme.background.transparent.light};
+ border-radius: 50px;
+ color: ${({ theme }) => theme.font.color.light};
+ display: flex;
+ font-size: ${({ theme }) => theme.font.size.xs};
+ font-style: normal;
+ font-weight: ${({ theme }) => theme.font.weight.medium};
+ gap: ${({ theme }) => theme.spacing(2)};
+ height: ${({ theme }) => theme.spacing(4)};
+ justify-content: flex-end;
+ line-height: ${({ theme }) => theme.text.lineHeight.lg};
+ margin-left: auto;
+ padding: ${({ theme }) => theme.spacing(1)} ${({ theme }) => theme.spacing(2)};
+`;
+
+export function SoonPill() {
+ return Soon;
+}
diff --git a/front/src/modules/ui/components/buttons/Button.tsx b/front/src/modules/ui/components/buttons/Button.tsx
index 45d70ae2dc..a203c72e9d 100644
--- a/front/src/modules/ui/components/buttons/Button.tsx
+++ b/front/src/modules/ui/components/buttons/Button.tsx
@@ -3,7 +3,9 @@ import styled from '@emotion/styled';
import { rgba } from '@/ui/themes/colors';
-type Variant =
+import { SoonPill } from '../accessories/SoonPill';
+
+export type ButtonVariant =
| 'primary'
| 'secondary'
| 'tertiary'
@@ -11,18 +13,22 @@ type Variant =
| 'tertiaryLight'
| 'danger';
-type Size = 'medium' | 'small';
+export type ButtonSize = 'medium' | 'small';
-type Props = {
+export type ButtonPosition = 'left' | 'middle' | 'right' | undefined;
+
+export type ButtonProps = {
icon?: React.ReactNode;
title?: string;
fullWidth?: boolean;
- variant?: Variant;
- size?: Size;
+ variant?: ButtonVariant;
+ size?: ButtonSize;
+ position?: ButtonPosition;
+ soon?: boolean;
} & React.ComponentProps<'button'>;
const StyledButton = styled.button<
- Pick
+ Pick
>`
align-items: center;
background: ${({ theme, variant, disabled }) => {
@@ -49,7 +55,18 @@ const StyledButton = styled.button<
return 'none';
}
}};
- border-radius: 4px;
+ border-radius: ${({ position }) => {
+ switch (position) {
+ case 'left':
+ return '4px 0px 0px 4px';
+ case 'right':
+ return '0px 4px 4px 0px';
+ case 'middle':
+ return '0px';
+ default:
+ return '4px';
+ }
+ }};
box-shadow: ${({ theme, variant }) => {
switch (variant) {
case 'primary':
@@ -59,6 +76,7 @@ const StyledButton = styled.button<
return 'none';
}
}};
+
color: ${({ theme, variant, disabled }) => {
if (disabled) {
if (variant === 'primary') {
@@ -105,6 +123,8 @@ const StyledButton = styled.button<
transition: background 0.1s ease;
+ white-space: nowrap;
+
width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')};
&:hover,
@@ -144,18 +164,24 @@ export function Button({
fullWidth = false,
variant = 'primary',
size = 'medium',
+ position,
+ soon = false,
+ disabled = false,
...props
-}: Props) {
+}: ButtonProps) {
return (
{icon}
{title}
+ {soon && }
);
}
diff --git a/front/src/modules/ui/components/buttons/ButtonGroup.tsx b/front/src/modules/ui/components/buttons/ButtonGroup.tsx
new file mode 100644
index 0000000000..4028d8b0fd
--- /dev/null
+++ b/front/src/modules/ui/components/buttons/ButtonGroup.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import styled from '@emotion/styled';
+
+import { ButtonPosition, ButtonProps } from './Button';
+
+const StyledButtonGroupContainer = styled.div`
+ border-radius: 8px;
+ display: flex;
+`;
+
+type ButtonGroupProps = Pick & {
+ children: React.ReactElement[];
+};
+
+export function ButtonGroup({ children, variant, size }: ButtonGroupProps) {
+ return (
+
+ {React.Children.map(children, (child, index) => {
+ let position: ButtonPosition;
+
+ if (index === 0) {
+ position = 'left';
+ } else if (index === children.length - 1) {
+ position = 'right';
+ } else {
+ position = 'middle';
+ }
+
+ const additionalProps: any = { position };
+
+ if (variant) {
+ additionalProps.variant = variant;
+ }
+
+ if (size) {
+ additionalProps.size = size;
+ }
+
+ return React.cloneElement(child, additionalProps);
+ })}
+
+ );
+}
diff --git a/front/src/modules/ui/components/buttons/MainButton.tsx b/front/src/modules/ui/components/buttons/MainButton.tsx
index 09b5cae87f..1142390840 100644
--- a/front/src/modules/ui/components/buttons/MainButton.tsx
+++ b/front/src/modules/ui/components/buttons/MainButton.tsx
@@ -8,6 +8,7 @@ type Props = {
title: string;
fullWidth?: boolean;
variant?: Variant;
+ soon?: boolean;
} & React.ComponentProps<'button'>;
const StyledButton = styled.button>`
diff --git a/front/src/modules/ui/components/buttons/__stories__/Button.stories.tsx b/front/src/modules/ui/components/buttons/__stories__/Button.stories.tsx
index add0b93c3d..f630b4e37d 100644
--- a/front/src/modules/ui/components/buttons/__stories__/Button.stories.tsx
+++ b/front/src/modules/ui/components/buttons/__stories__/Button.stories.tsx
@@ -1,3 +1,4 @@
+import React from 'react';
import styled from '@emotion/styled';
import { text, withKnobs } from '@storybook/addon-knobs';
import { expect, jest } from '@storybook/jest';
@@ -8,6 +9,7 @@ import { IconSearch } from '@/ui/icons';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { Button } from '../Button';
+import { ButtonGroup } from '../ButtonGroup';
type ButtonProps = React.ComponentProps;
@@ -62,8 +64,6 @@ const meta: Meta = {
export default meta;
type Story = StoryObj;
-const clickJestFn = jest.fn();
-
const variants: ButtonProps['variant'][] = [
'primary',
'secondary',
@@ -73,148 +73,157 @@ const variants: ButtonProps['variant'][] = [
'danger',
];
-const ButtonLine = (props: ButtonProps) => (
+const clickJestFn = jest.fn();
+
+const states = {
+ 'with-icon': {
+ description: 'With icon',
+ extraProps: (variant: string) => ({
+ 'data-testid': `${variant}-button-with-icon`,
+ icon: ,
+ }),
+ },
+ default: {
+ description: 'Default',
+ extraProps: (variant: string) => ({
+ 'data-testid': `${variant}-button-default`,
+ onClick: clickJestFn,
+ }),
+ },
+ hover: {
+ description: 'Hover',
+ extraProps: (variant: string) => ({
+ id: `${variant}-button-hover`,
+ 'data-testid': `${variant}-button-hover`,
+ }),
+ },
+ pressed: {
+ description: 'Pressed',
+ extraProps: (variant: string) => ({
+ id: `${variant}-button-pressed`,
+ 'data-testid': `${variant}-button-pressed`,
+ }),
+ },
+ disabled: {
+ description: 'Disabled',
+ extraProps: (variant: string) => ({
+ 'data-testid': `${variant}-button-disabled`,
+ disabled: true,
+ }),
+ },
+ soon: {
+ description: 'Soon',
+ extraProps: (variant: string) => ({
+ 'data-testid': `${variant}-button-soon`,
+ soon: true,
+ }),
+ },
+ focus: {
+ description: 'Focus',
+ extraProps: (variant: string) => ({
+ id: `${variant}-button-focus`,
+ 'data-testid': `${variant}-button-focus`,
+ }),
+ },
+};
+
+const ButtonLine: React.FC = ({ variant, ...props }) => (
<>
-
- With icon
- }
- />
-
-
- Default
-
-
-
- Hover
-
-
-
- Pressed
-
-
-
- Disabled
-
-
-
- Focus
-
-
+ {Object.entries(states).map(([state, { description, extraProps }]) => (
+
+ {description}
+
+
+ ))}
>
);
-const ButtonContainer = (props: Partial) => {
- const title = text('Text', 'A button title');
+const ButtonGroupLine: React.FC = ({ variant, ...props }) => (
+ <>
+ {Object.entries(states).map(([state, { description, extraProps }]) => (
+
+ {description}
+
+
+
+
+
+
+ ))}
+ >
+);
- return (
+const generateStory = (
+ size: ButtonProps['size'],
+ type: 'button' | 'group',
+ LineComponent: React.ComponentType,
+): Story => ({
+ render: getRenderWrapperForComponent(
{variants.map((variant) => (
{variant}
-
+
))}
-
- );
-};
-
-// Medium size
-export const MediumSize: Story = {
- render: getRenderWrapperForComponent(),
+ ,
+ ),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
+ let button;
+ if (type === 'group') {
+ button = canvas.getByTestId(`primary-left-button-default`);
+ } else {
+ button = canvas.getByTestId(`primary-button-default`);
+ }
- expect(clickJestFn).toHaveBeenCalledTimes(0);
-
- const button = canvas.getByTestId('primary-button-default');
+ const numberOfClicks = clickJestFn.mock.calls.length;
await userEvent.click(button);
+ expect(clickJestFn).toHaveBeenCalledTimes(numberOfClicks + 1);
+ },
+ parameters: {
+ pseudo: Object.keys(states).reduce(
+ (acc, state) => ({
+ ...acc,
+ [state]: variants.map(
+ (variant) =>
+ variant &&
+ ['#left', '#center', '#right'].map(
+ (pos) => `${pos}-${variant}-${type}-${state}`,
+ ),
+ ),
+ }),
+ {},
+ ),
+ },
+});
- expect(clickJestFn).toHaveBeenCalledTimes(1);
- },
-};
-MediumSize.parameters = {
- pseudo: {
- hover: [
- '#primary-button-hover',
- '#secondary-button-hover',
- '#tertiary-button-hover',
- '#tertiaryBold-button-hover',
- '#tertiaryLight-button-hover',
- '#danger-button-hover',
- ],
- active: [
- '#primary-button-pressed',
- '#secondary-button-pressed',
- '#tertiary-button-pressed',
- '#tertiaryBold-button-pressed',
- '#tertiaryLight-button-pressed',
- '#danger-button-pressed',
- ],
- focus: [
- '#primary-button-focus',
- '#secondary-button-focus',
- '#tertiary-button-focus',
- '#tertiaryBold-button-focus',
- '#tertiaryLight-button-focus',
- '#danger-button-focus',
- ],
- },
-};
-
-// Small size
-export const SmallSize: Story = {
- render: getRenderWrapperForComponent(),
-};
-SmallSize.parameters = {
- pseudo: {
- hover: [
- '#primary-button-hover',
- '#secondary-button-hover',
- '#tertiary-button-hover',
- '#tertiaryBold-button-hover',
- '#tertiaryLight-button-hover',
- '#danger-button-hover',
- ],
- active: [
- '#primary-button-pressed',
- '#secondary-button-pressed',
- '#tertiary-button-pressed',
- '#tertiaryBold-button-pressed',
- '#tertiaryLight-button-pressed',
- '#danger-button-pressed',
- ],
- focus: [
- '#primary-button-focus',
- '#secondary-button-focus',
- '#tertiary-button-focus',
- '#tertiaryBold-button-focus',
- '#tertiaryLight-button-focus',
- '#danger-button-focus',
- ],
- },
-};
+export const MediumSize = generateStory('medium', 'button', ButtonLine);
+export const SmallSize = generateStory('small', 'button', ButtonLine);
+export const MediumSizeGroup = generateStory(
+ 'medium',
+ 'group',
+ ButtonGroupLine,
+);
+export const SmallSizeGroup = generateStory('small', 'group', ButtonGroupLine);
diff --git a/front/src/modules/ui/components/table/action-bar/TableActionBarButtonOpenComments.tsx b/front/src/modules/ui/components/table/action-bar/TableActionBarButtonOpenComments.tsx
index f908ff700a..cdc93bf837 100644
--- a/front/src/modules/ui/components/table/action-bar/TableActionBarButtonOpenComments.tsx
+++ b/front/src/modules/ui/components/table/action-bar/TableActionBarButtonOpenComments.tsx
@@ -9,7 +9,7 @@ type OwnProps = {
export function TableActionBarButtonToggleComments({ onClick }: OwnProps) {
return (
}
onClick={onClick}
/>
diff --git a/front/src/modules/ui/icons/index.ts b/front/src/modules/ui/icons/index.ts
index 6440036015..82aa569492 100644
--- a/front/src/modules/ui/icons/index.ts
+++ b/front/src/modules/ui/icons/index.ts
@@ -33,3 +33,5 @@ export { IconFileUpload } from '@tabler/icons-react';
export { IconChevronsRight } from '@tabler/icons-react';
export { IconNotes } from '@tabler/icons-react';
export { IconCirclePlus } from '@tabler/icons-react';
+export { IconCheckbox } from '@tabler/icons-react';
+export { IconTimelineEvent } from '@tabler/icons-react';
diff --git a/front/src/modules/ui/layout/navbar/NavItem.tsx b/front/src/modules/ui/layout/navbar/NavItem.tsx
index 60417f8041..186c4a7c53 100644
--- a/front/src/modules/ui/layout/navbar/NavItem.tsx
+++ b/front/src/modules/ui/layout/navbar/NavItem.tsx
@@ -65,16 +65,16 @@ const StyledItemLabel = styled.div`
`;
const StyledSoonPill = styled.div`
- display: flex;
- justify-content: center;
align-items: center;
- border-radius: 50px;
background-color: ${({ theme }) => theme.background.transparent.light};
+ border-radius: 50px;
+ display: flex;
font-size: ${({ theme }) => theme.font.size.xs};
height: 16px;
+ justify-content: center;
+ margin-left: auto;
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(2)};
- margin-left: auto; // this aligns the pill to the right
`;
function NavItem({ label, icon, to, onClick, active, danger, soon }: OwnProps) {
diff --git a/front/src/pages/companies/__stories__/Company.stories.tsx b/front/src/pages/companies/__stories__/Company.stories.tsx
index b8dd4f8942..fe57955f96 100644
--- a/front/src/pages/companies/__stories__/Company.stories.tsx
+++ b/front/src/pages/companies/__stories__/Company.stories.tsx
@@ -32,7 +32,7 @@ export const Default: Story = {
),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
- const notesButton = await canvas.findByText('Notes');
+ const notesButton = await canvas.findByText('Note');
await notesButton.click();
},
parameters: {