Uniformize folder structure (#693)

* Uniformize folder structure

* Fix icons

* Fix icons

* Fix tests

* Fix tests
This commit is contained in:
Charles Bochet
2023-07-16 14:29:28 -07:00
committed by GitHub
parent 900ec5572f
commit 6ced8434bd
462 changed files with 931 additions and 960 deletions

View File

@ -0,0 +1,133 @@
import { useState } from 'react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
import { useEditableField } from '../hooks/useEditableField';
import { EditableFieldDisplayMode } from './EditableFieldDisplayMode';
import { EditableFieldEditButton } from './EditableFieldEditButton';
import { EditableFieldEditMode } from './EditableFieldEditMode';
const StyledIconContainer = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
svg {
align-items: center;
display: flex;
height: 16px;
justify-content: center;
width: 16px;
}
`;
const StyledLabelAndIconContainer = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
`;
const StyledLabel = styled.div<Pick<OwnProps, 'labelFixedWidth'>>`
align-items: center;
width: ${({ labelFixedWidth }) =>
labelFixedWidth ? `${labelFixedWidth}px` : 'fit-content'};
`;
export const EditableFieldBaseContainer = styled.div`
align-items: center;
box-sizing: border-box;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
height: 24px;
position: relative;
user-select: none;
width: 100%;
`;
type OwnProps = {
iconLabel?: React.ReactNode;
label?: string;
labelFixedWidth?: number;
useEditButton?: boolean;
editModeContent: React.ReactNode;
displayModeContent: React.ReactNode;
parentHotkeyScope?: HotkeyScope;
customEditHotkeyScope?: HotkeyScope;
onSubmit?: () => void;
onCancel?: () => void;
};
export function EditableField({
iconLabel,
label,
labelFixedWidth,
useEditButton,
editModeContent,
displayModeContent,
parentHotkeyScope,
customEditHotkeyScope,
onSubmit,
onCancel,
}: OwnProps) {
const [isHovered, setIsHovered] = useState(false);
function handleContainerMouseEnter() {
setIsHovered(true);
}
function handleContainerMouseLeave() {
setIsHovered(false);
}
const { isFieldInEditMode, openEditableField } =
useEditableField(parentHotkeyScope);
function handleDisplayModeClick() {
openEditableField(customEditHotkeyScope);
}
const showEditButton = !isFieldInEditMode && isHovered && useEditButton;
return (
<EditableFieldBaseContainer
onMouseEnter={handleContainerMouseEnter}
onMouseLeave={handleContainerMouseLeave}
>
<StyledLabelAndIconContainer>
{iconLabel && <StyledIconContainer>{iconLabel}</StyledIconContainer>}
{label && (
<StyledLabel labelFixedWidth={labelFixedWidth}>{label}</StyledLabel>
)}
</StyledLabelAndIconContainer>
{isFieldInEditMode ? (
<EditableFieldEditMode onSubmit={onSubmit} onCancel={onCancel}>
{editModeContent}
</EditableFieldEditMode>
) : (
<EditableFieldDisplayMode
disableClick={useEditButton}
onClick={handleDisplayModeClick}
>
{displayModeContent}
</EditableFieldDisplayMode>
)}
{showEditButton && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.1 }}
whileHover={{ scale: 1.04 }}
>
<EditableFieldEditButton customHotkeyScope={customEditHotkeyScope} />
</motion.div>
)}
</EditableFieldBaseContainer>
);
}

View File

@ -0,0 +1,71 @@
import { css } from '@emotion/react';
import styled from '@emotion/styled';
export const EditableFieldNormalModeOuterContainer = styled.div<
Pick<OwnProps, 'disableClick'>
>`
align-items: center;
border-radius: ${({ theme }) => theme.border.radius.sm};
display: flex;
height: 100%;
height: 16px;
overflow: hidden;
padding: ${({ theme }) => theme.spacing(1)};
width: 100%;
${(props) => {
if (props.disableClick) {
return css`
cursor: default;
`;
} else {
return css`
cursor: pointer;
&:hover {
background-color: ${props.theme.background.transparent.light};
}
`;
}
}}
`;
export const EditableFieldNormalModeInnerContainer = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.md};
font-weight: ${({ theme }) => theme.font.weight.regular};
height: fit-content;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
type OwnProps = {
disableClick?: boolean;
onClick?: () => void;
};
export function EditableFieldDisplayMode({
children,
disableClick,
onClick,
}: React.PropsWithChildren<OwnProps>) {
return (
<EditableFieldNormalModeOuterContainer
onClick={disableClick ? undefined : onClick}
disableClick={disableClick}
>
<EditableFieldNormalModeInnerContainer>
{children}
</EditableFieldNormalModeInnerContainer>
</EditableFieldNormalModeOuterContainer>
);
}

View File

@ -0,0 +1,49 @@
import styled from '@emotion/styled';
import { IconButton } from '@/ui/button/components/IconButton';
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
import { IconPencil } from '@/ui/icon';
import { overlayBackground } from '@/ui/themes/effects';
import { useEditableField } from '../hooks/useEditableField';
export const StyledEditableFieldEditButton = styled.div`
align-items: center;
border: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ theme }) => theme.font.color.tertiary};
cursor: pointer;
display: flex;
height: 20px;
justify-content: center;
margin-left: -2px;
width: 20px;
z-index: 1;
${overlayBackground}
`;
type OwnProps = {
customHotkeyScope?: HotkeyScope;
};
export function EditableFieldEditButton({ customHotkeyScope }: OwnProps) {
const { openEditableField } = useEditableField();
function handleClick() {
openEditableField(customHotkeyScope);
}
return (
<IconButton
variant="shadow"
size="small"
onClick={handleClick}
icon={<IconPencil size={14} />}
data-testid="editable-field-edit-mode-container"
/>
);
}

View File

@ -0,0 +1,41 @@
import { useRef } from 'react';
import styled from '@emotion/styled';
import { useRegisterCloseFieldHandlers } from '../hooks/useRegisterCloseFieldHandlers';
export const EditableFieldEditModeContainer = styled.div<OwnProps>`
align-items: center;
display: flex;
margin-left: -${({ theme }) => theme.spacing(1)};
width: inherit;
z-index: 10;
`;
type OwnProps = {
children: React.ReactNode;
onOutsideClick?: () => void;
onCancel?: () => void;
onSubmit?: () => void;
};
export function EditableFieldEditMode({
children,
onCancel,
onSubmit,
}: OwnProps) {
const wrapperRef = useRef(null);
useRegisterCloseFieldHandlers(wrapperRef, onSubmit, onCancel);
return (
<EditableFieldEditModeContainer
data-testid="editable-field-edit-mode-container"
ref={wrapperRef}
>
{children}
</EditableFieldEditModeContainer>
);
}

View File

@ -0,0 +1,60 @@
import { useEffect, useState } from 'react';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { IconMap } from '@/ui/icon';
import { InplaceInputText } from '@/ui/inplace-input/components/InplaceInputText';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import { Company, useUpdateCompanyMutation } from '~/generated/graphql';
type OwnProps = {
company: Pick<Company, 'id' | 'address'>;
};
export function CompanyEditableFieldAddress({ company }: OwnProps) {
const [internalValue, setInternalValue] = useState(company.address);
const [updateCompany] = useUpdateCompanyMutation();
useEffect(() => {
setInternalValue(company.address);
}, [company.address]);
async function handleChange(newValue: string) {
setInternalValue(newValue);
}
async function handleSubmit() {
await updateCompany({
variables: {
id: company.id,
address: internalValue ?? '',
},
});
}
async function handleCancel() {
setInternalValue(company.address);
}
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
onSubmit={handleSubmit}
onCancel={handleCancel}
iconLabel={<IconMap />}
editModeContent={
<InplaceInputText
placeholder={'Address'}
autoFocus
value={internalValue}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
}
displayModeContent={internalValue !== '' ? internalValue : 'No address'}
/>
</RecoilScope>
);
}

View File

@ -0,0 +1,5 @@
import { RawLink } from '@/ui/link/components/RawLink';
export function FieldDisplayURL({ URL }: { URL: string | undefined }) {
return <RawLink href={URL ? 'https://' + URL : ''}>{URL}</RawLink>;
}