From fea48bf12f430916f8f07a7494841d11dc1a7bf5 Mon Sep 17 00:00:00 2001 From: Katrinputrina Date: Thu, 31 Aug 2023 11:44:28 +0300 Subject: [PATCH] feat: inlineForm component --- src/components/InlineForm.tsx | 122 +++++++++++++++++++++++++++++ src/components/index.tsx | 1 + src/stories/InlineForm.stories.tsx | 110 ++++++++++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 src/components/InlineForm.tsx create mode 100644 src/stories/InlineForm.stories.tsx diff --git a/src/components/InlineForm.tsx b/src/components/InlineForm.tsx new file mode 100644 index 00000000..cabcfb45 --- /dev/null +++ b/src/components/InlineForm.tsx @@ -0,0 +1,122 @@ +import React, { useEffect, useMemo, useReducer, useRef, useState } from 'react'; +import styled from 'styled-components'; +import { IconQuestionCircleOutline } from '@taskany/icons'; + +import { KeyCode, useKeyboard } from '../hooks/useKeyboard'; +import { useClickOutside } from '../hooks/useClickOutside'; +import { nullable } from '../utils'; + +import { Form } from './Form'; +import { Popup } from './Popup'; + +interface RenderTriggerProps { + onClick: () => void; +} + +interface InlineFormProps { + renderTrigger?: (props: RenderTriggerProps) => React.ReactNode; + onSubmit: () => void; + children: React.ReactNode; + tip?: React.ReactNode; + onReset: () => void; + isSubmitted?: boolean; + className?: string; +} + +const StyledWrapper = styled.div` + display: contents; + + & form { + background-color: transparent; + display: contents; + } +`; + +const StyledFormWrapper = styled.div` + display: contents; + position: relative; +`; + +const StyledQuestionIcon = styled(IconQuestionCircleOutline)` + margin-left: 4px; +`; + +export const InlineForm: React.FC = ({ + renderTrigger, + onSubmit, + onReset, + isSubmitted, + children, + tip, + className, +}) => { + const wrapperRef = useRef(null); + const [visible, toggle] = useReducer((state) => !state, !renderTrigger); + const [hintVisible, toggleHintVisible] = useState(false); + const hintRef = useRef(null); + const trigger = useMemo(() => { + if (typeof renderTrigger === 'function') { + return renderTrigger({ onClick: toggle }); + } + + return null; + }, [renderTrigger]); + + const [onESC] = useKeyboard( + [KeyCode.Escape], + () => { + if (visible) { + toggle(); + } + }, + { + capture: true, + }, + ); + + useClickOutside(wrapperRef, () => { + if (visible) { + toggle(); + } + }); + + useEffect(() => { + if (isSubmitted) { + toggle(); + onReset(); + } + }, [onReset, isSubmitted]); + + useEffect(() => { + if (!visible) { + onReset(); + } + }, [visible, onReset]); + + return ( + + {!visible && trigger} + {visible && ( +
+ + {children} + {nullable(tip, (t: React.ReactNode) => ( + <> + toggleHintVisible(true)} + onMouseLeave={() => toggleHintVisible(false)} + > + + + + {t} + + + ))} + +
+ )} +
+ ); +}; diff --git a/src/components/index.tsx b/src/components/index.tsx index 8fff4d7c..f90bef1b 100644 --- a/src/components/index.tsx +++ b/src/components/index.tsx @@ -53,3 +53,4 @@ export * from './Popup'; export * from './ListView'; export * from './ErrorPopup'; export * from './Spinner'; +export * from './InlineForm'; diff --git a/src/stories/InlineForm.stories.tsx b/src/stories/InlineForm.stories.tsx new file mode 100644 index 00000000..3126fef3 --- /dev/null +++ b/src/stories/InlineForm.stories.tsx @@ -0,0 +1,110 @@ +import React, { useCallback } from 'react'; +import { Meta, StoryObj } from '@storybook/react'; +import styled from 'styled-components'; +import { IconQuestionCircleOutline, IconTargetOutline } from '@taskany/icons'; +import { gray7 } from '@taskany/colors'; + +import { InlineForm } from '../components/InlineForm'; +import { Button } from '../components/Button'; +import { TableCell } from '../components/Table'; +import { Form } from '../components/Form'; +import { Popup } from '../components/Popup'; +import { FormInput } from '../components/FormInput'; + +const meta: Meta = { + title: 'InlineForm', + component: InlineForm, +}; + +export default meta; + +type Story = StoryObj; + +const StyledWrapper = styled.div` + display: contents; + + & form { + background-color: transparent; + display: contents; + } +`; + +const StyledFormWrapper = styled.div` + display: contents; + position: relative; +`; + +const StyledTableCell = styled(TableCell)` + flex-wrap: nowrap; + display: flex; + align-items: center; +`; + +const StyledFormInput = styled(FormInput)` + font-size: 14px; + font-weight: normal; + padding: 5px 10px; + flex: 1; + + border: 1px solid ${gray7}; + box-sizing: border-box; +`; + +const StyledQuestionIcon = styled(IconQuestionCircleOutline)` + margin-left: 4px; +`; + +export const InlineFormComponent: Story = () => { + const onClick = () => { + console.log('Clicked'); + }; + + const handleFormInputChange = useCallback(() => { + console.log('Change Click'); + }, []); + + return ( + +
console.log()}> + + +