Lucas/t 223 i can add comments to companies or people using the right (#181)

* wip

* Implemented comment input text component

* Improved behavior
This commit is contained in:
Lucas Bordeau
2023-06-01 19:49:37 +02:00
committed by GitHub
parent f906533b29
commit bf3097500a
13 changed files with 339 additions and 6 deletions

View File

@ -26,6 +26,7 @@
"react-hotkeys-hook": "^4.4.0",
"react-icons": "^4.8.0",
"react-router-dom": "^6.4.4",
"react-textarea-autosize": "^8.4.1",
"recoil": "^0.7.7",
"uuid": "^9.0.0",
"web-vitals": "^2.1.4"
@ -31481,6 +31482,22 @@
"node": ">=10"
}
},
"node_modules/react-textarea-autosize": {
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.4.1.tgz",
"integrity": "sha512-aD2C+qK6QypknC+lCMzteOdIjoMbNlgSFmJjCV+DrfTPwp59i/it9mMNf2HDzvRjQgKAyBDPyLJhcrzElf2U4Q==",
"dependencies": {
"@babel/runtime": "^7.20.13",
"use-composed-ref": "^1.3.0",
"use-latest": "^1.2.1"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -34999,6 +35016,43 @@
"integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==",
"dev": true
},
"node_modules/use-composed-ref": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz",
"integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/use-isomorphic-layout-effect": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
"integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-latest": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz",
"integrity": "sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==",
"dependencies": {
"use-isomorphic-layout-effect": "^1.1.1"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-resize-observer": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-9.1.0.tgz",

View File

@ -21,6 +21,7 @@
"react-hotkeys-hook": "^4.4.0",
"react-icons": "^4.8.0",
"react-router-dom": "^6.4.4",
"react-textarea-autosize": "^8.4.1",
"recoil": "^0.7.7",
"uuid": "^9.0.0",
"web-vitals": "^2.1.4"

View File

@ -0,0 +1,36 @@
import styled from '@emotion/styled';
const StyledIconButton = styled.button`
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
padding: 0;
border: none;
border-radius: 50%;
background: ${(props) => props.theme.text80};
color: ${(props) => props.theme.text100};
transition: color 0.1s ease-in-out, background 0.1s ease-in-out;
background: ${(props) => props.theme.blue};
color: ${(props) => props.theme.text0};
cursor: pointer;
&:disabled {
background: ${(props) => props.theme.quadraryBackground};
color: ${(props) => props.theme.text80};
cursor: default;
}
`;
export function IconButton({
icon,
...props
}: { icon: React.ReactNode } & React.ButtonHTMLAttributes<HTMLButtonElement>) {
return <StyledIconButton {...props}>{icon}</StyledIconButton>;
}

View File

@ -0,0 +1,122 @@
import styled from '@emotion/styled';
import { useState } from 'react';
import { HiArrowSmRight } from 'react-icons/hi';
import TextareaAutosize from 'react-textarea-autosize';
import { IconButton } from '../buttons/IconButton';
import { useHotkeys } from 'react-hotkeys-hook';
import { HotkeysEvent } from 'react-hotkeys-hook/dist/types';
type OwnProps = {
onSend?: (text: string) => void;
placeholder?: string;
};
const StyledContainer = styled.div`
display: flex;
min-height: 32px;
width: 100%;
`;
const StyledTextArea = styled(TextareaAutosize)`
width: 100%;
padding: 8px;
font-size: 13px;
font-family: inherit;
font-weight: 400;
line-height: 16px;
border: none;
border-radius: 5px;
background: ${(props) => props.theme.tertiaryBackground};
color: ${(props) => props.theme.text80};
overflow: auto;
resize: none;
&:focus {
outline: none;
border: none;
}
&::placeholder {
color: ${(props) => props.theme.text30};
font-weight: 400;
}
`;
const StyledBottomRightIconButton = styled.div`
width: 0px;
position: relative;
top: calc(100% - 26.5px);
right: 26px;
`;
export function CommentTextInput({ placeholder, onSend }: OwnProps) {
const [text, setText] = useState('');
const isSendButtonDisabled = !text;
useHotkeys(
['shift+enter', 'enter'],
(event: KeyboardEvent, handler: HotkeysEvent) => {
if (handler.shift) {
return;
} else {
event.preventDefault();
onSend?.(text);
setText('');
}
},
{
enableOnContentEditable: true,
enableOnFormTags: true,
},
[onSend],
);
useHotkeys(
'esc',
(event: KeyboardEvent, handler: HotkeysEvent) => {
event.preventDefault();
setText('');
},
{
enableOnContentEditable: true,
enableOnFormTags: true,
},
[onSend],
);
function handleInputChange(event: React.FormEvent<HTMLTextAreaElement>) {
const newText = event.currentTarget.value;
setText(newText);
}
function handleOnClickSendButton() {
onSend?.(text);
setText('');
}
return (
<>
<StyledContainer>
<StyledTextArea
placeholder={placeholder || 'Write something...'}
maxRows={5}
onChange={handleInputChange}
value={text}
/>
<StyledBottomRightIconButton>
<IconButton
onClick={handleOnClickSendButton}
icon={<HiArrowSmRight size={15} />}
disabled={isSendButtonDisabled}
/>
</StyledBottomRightIconButton>
</StyledContainer>
</>
);
}

View File

@ -1,9 +1,19 @@
import { RightDrawerBody } from '../../layout/right-drawer/RightDrawerBody';
import { RightDrawerPage } from '../../layout/right-drawer/RightDrawerPage';
import { RightDrawerTopBar } from '../../layout/right-drawer/RightDrawerTopBar';
import { CommentTextInput } from './CommentTextInput';
export function RightDrawerComments() {
function handleSendComment(text: string) {
console.log(text);
}
return (
<>
<RightDrawerPage>
<RightDrawerTopBar title="Comments" />
</>
<RightDrawerBody>
<CommentTextInput onSend={handleSendComment} />
</RightDrawerBody>
</RightDrawerPage>
);
}

View File

@ -0,0 +1,30 @@
import type { Meta, StoryObj } from '@storybook/react';
import { graphqlMocks } from '../../../testing/graphqlMocks';
import { CommentTextInput } from '../CommentTextInput';
import { getRenderWrapperForComponent } from '../../../testing/renderWrappers';
const meta: Meta<typeof CommentTextInput> = {
title: 'Components/CommentTextInput',
component: CommentTextInput,
argTypes: {
onSend: {
action: 'onSend',
},
},
};
export default meta;
type Story = StoryObj<typeof CommentTextInput>;
export const Default: Story = {
render: getRenderWrapperForComponent(<CommentTextInput />),
parameters: {
msw: graphqlMocks,
actions: { argTypesRegex: '^on.*' },
},
args: {
onSend: (text: string) => {
console.log(text);
},
},
};

View File

@ -0,0 +1,8 @@
import styled from '@emotion/styled';
export const RightDrawerBody = styled.div`
display: flex;
flex-direction: column;
padding: 8px;
`;

View File

@ -0,0 +1,8 @@
import styled from '@emotion/styled';
export const RightDrawerPage = styled.div`
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
`;

View File

@ -12,7 +12,6 @@ const StyledRightDrawerTopBar = styled.div`
font-size: 13px;
color: ${(props) => props.theme.text60};
border-bottom: 1px solid ${(props) => props.theme.lightBorder};
width: 100%;
`;
const StyledTopBarTitle = styled.div`

View File

@ -27,6 +27,8 @@ const lightThemeSpecific = {
primaryBackground: '#fff',
secondaryBackground: '#fcfcfc',
tertiaryBackground: '#f5f5f5',
quadraryBackground: '#ebebeb',
pinkBackground: '#ffe5f4',
greenBackground: '#e6fff2',
purpleBackground: '#e0e0ff',
@ -64,6 +66,8 @@ const darkThemeSpecific: typeof lightThemeSpecific = {
primaryBackground: '#141414',
secondaryBackground: '#171717',
tertiaryBackground: '#333333',
quadraryBackground: '#444444',
pinkBackground: '#cc0078',
greenBackground: '#1e7e50',
purpleBackground: '#1111b7',

View File

@ -2,7 +2,8 @@ import type { Meta, StoryObj } from '@storybook/react';
import Companies from '../Companies';
import { render, mocks } from './shared';
import { getRenderWrapperForPage } from '../../../testing/renderWrappers';
import { graphqlMocks } from '../../../testing/graphqlMocks';
const meta: Meta<typeof Companies> = {
title: 'Pages/Companies',
@ -14,8 +15,8 @@ export default meta;
export type Story = StoryObj<typeof Companies>;
export const Default: Story = {
render,
render: getRenderWrapperForPage(<Companies />),
parameters: {
msw: mocks,
msw: graphqlMocks,
},
};

View File

@ -0,0 +1,21 @@
import styled from '@emotion/styled';
const StyledLayout = styled.div`
display: flex;
flex-direction: row;
width: fit-content;
height: fit-content;
padding: 20px;
background: ${(props) => props.theme.primaryBackground};
border-radius: 5px;
box-shadow: 0px 0px 2px;
`;
type OwnProps = {
children: JSX.Element;
};
export function ComponentStorybookLayout({ children }: OwnProps) {
return <StyledLayout>{children}</StyledLayout>;
}

View File

@ -0,0 +1,39 @@
import { MemoryRouter } from 'react-router-dom';
import { FullHeightStorybookLayout } from './FullHeightStorybookLayout';
import { ThemeProvider } from '@emotion/react';
import { ApolloProvider } from '@apollo/client';
import { RecoilRoot } from 'recoil';
import { mockedClient } from './mockedClient';
import { lightTheme } from '../layout/styles/themes';
import React from 'react';
import { ComponentStorybookLayout } from './ComponentStorybookLayout';
export function getRenderWrapperForPage(children: React.ReactElement) {
return function render() {
return (
<RecoilRoot>
<ApolloProvider client={mockedClient}>
<ThemeProvider theme={lightTheme}>
<MemoryRouter>
<FullHeightStorybookLayout>{children}</FullHeightStorybookLayout>
</MemoryRouter>
</ThemeProvider>
</ApolloProvider>
</RecoilRoot>
);
};
}
export function getRenderWrapperForComponent(children: React.ReactElement) {
return function render() {
return (
<RecoilRoot>
<ApolloProvider client={mockedClient}>
<ThemeProvider theme={lightTheme}>
<ComponentStorybookLayout>{children}</ComponentStorybookLayout>
</ThemeProvider>
</ApolloProvider>
</RecoilRoot>
);
};
}