mirror of
https://github.com/twentyhq/twenty.git
synced 2025-01-05 10:54:15 +03:00
parent
8b7314cd39
commit
5b21657c4e
@ -1,70 +1,21 @@
|
|||||||
import { Link } from 'react-router-dom';
|
import { EntityChip } from '@/ui/chip/components/EntityChip';
|
||||||
import { Theme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
|
|
||||||
import { Avatar } from '@/users/components/Avatar';
|
type OwnProps = {
|
||||||
|
|
||||||
export type CompanyChipPropsType = {
|
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
picture?: string;
|
picture?: string;
|
||||||
|
clickable?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseStyle = ({ theme }: { theme: Theme }) => `
|
export function CompanyChip({ id, name, picture, clickable }: OwnProps) {
|
||||||
align-items: center;
|
|
||||||
background-color: ${theme.background.tertiary};
|
|
||||||
border-radius: ${theme.spacing(1)};
|
|
||||||
color: ${theme.font.color.primary};
|
|
||||||
display: inline-flex;
|
|
||||||
gap: ${theme.spacing(1)};
|
|
||||||
height: calc(20px - 2 * ${theme.spacing(1)});
|
|
||||||
overflow: hidden;
|
|
||||||
padding: ${theme.spacing(1)};
|
|
||||||
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
:hover {
|
|
||||||
filter: brightness(95%);
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
height: 14px;
|
|
||||||
object-fit: cover;
|
|
||||||
width: 14px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledName = styled.span`
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledContainerLink = styled(Link)`
|
|
||||||
${baseStyle}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledContainerNoLink = styled.div`
|
|
||||||
${baseStyle}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export function CompanyChip({ id, name, picture }: CompanyChipPropsType) {
|
|
||||||
const ContainerComponent = id ? StyledContainerLink : StyledContainerNoLink;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContainerComponent data-testid="company-chip" to={`/companies/${id}`}>
|
<EntityChip
|
||||||
{picture && (
|
entityId={id}
|
||||||
<Avatar
|
linkToEntity={`/companies/${id}`}
|
||||||
avatarUrl={picture?.toString()}
|
name={name}
|
||||||
colorId={id}
|
avatarType="squared"
|
||||||
placeholder={name}
|
clickable={clickable}
|
||||||
type="squared"
|
picture={picture}
|
||||||
size={14}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<StyledName>{name}</StyledName>
|
|
||||||
</ContainerComponent>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -29,10 +29,15 @@ export function CompanyEditableNameChipCell({ company }: OwnProps) {
|
|||||||
<EditableCellChip
|
<EditableCellChip
|
||||||
value={internalValue}
|
value={internalValue}
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
picture={getLogoUrlFromDomainName(company.domainName)}
|
|
||||||
id={company.id}
|
|
||||||
changeHandler={setInternalValue}
|
changeHandler={setInternalValue}
|
||||||
ChipComponent={CompanyChip}
|
ChipComponent={
|
||||||
|
<CompanyChip
|
||||||
|
id={company.id}
|
||||||
|
name={company.name}
|
||||||
|
clickable
|
||||||
|
picture={getLogoUrlFromDomainName(company.domainName)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
onSubmit={() =>
|
onSubmit={() =>
|
||||||
updateCompany({
|
updateCompany({
|
||||||
variables: {
|
variables: {
|
||||||
|
@ -25,15 +25,13 @@ export function CompanyAccountOwnerEditableField({ company }: OwnProps) {
|
|||||||
}}
|
}}
|
||||||
parentHotkeyScope={{
|
parentHotkeyScope={{
|
||||||
scope: PageHotkeyScope.CompanyShowPage,
|
scope: PageHotkeyScope.CompanyShowPage,
|
||||||
|
customScopes: {
|
||||||
|
goto: true,
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
iconLabel={<IconUserCircle />}
|
iconLabel={<IconUserCircle />}
|
||||||
editModeContent={
|
editModeContent={
|
||||||
<CompanyAccountOwnerPickerFieldEditMode
|
<CompanyAccountOwnerPickerFieldEditMode company={company} />
|
||||||
parentHotkeyScope={{
|
|
||||||
scope: PageHotkeyScope.CompanyShowPage,
|
|
||||||
}}
|
|
||||||
company={company}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
displayModeContent={
|
displayModeContent={
|
||||||
company.accountOwner?.displayName ? (
|
company.accountOwner?.displayName ? (
|
||||||
|
@ -24,9 +24,8 @@ export function CompanyAccountOwnerPickerFieldEditMode({
|
|||||||
company,
|
company,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
parentHotkeyScope,
|
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const { closeEditableField } = useEditableField(parentHotkeyScope);
|
const { closeEditableField } = useEditableField();
|
||||||
|
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
closeEditableField();
|
closeEditableField();
|
||||||
|
@ -54,6 +54,7 @@ export function CompanyAddressEditableField({ company }: OwnProps) {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
displayModeContent={internalValue ?? ''}
|
displayModeContent={internalValue ?? ''}
|
||||||
|
isDisplayModeContentEmpty={!(internalValue !== '')}
|
||||||
/>
|
/>
|
||||||
</RecoilScope>
|
</RecoilScope>
|
||||||
);
|
);
|
||||||
|
@ -51,6 +51,7 @@ export function EditablePeopleFullName({
|
|||||||
<PersonChip
|
<PersonChip
|
||||||
name={person?.firstName + ' ' + person?.lastName}
|
name={person?.firstName + ' ' + person?.lastName}
|
||||||
id={person?.id ?? ''}
|
id={person?.id ?? ''}
|
||||||
|
clickable
|
||||||
/>
|
/>
|
||||||
</NoEditModeContainer>
|
</NoEditModeContainer>
|
||||||
}
|
}
|
||||||
|
@ -1,66 +1,26 @@
|
|||||||
import * as React from 'react';
|
import { EntityChip } from '@/ui/chip/components/EntityChip';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { Theme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
|
|
||||||
import { Avatar } from '@/users/components/Avatar';
|
|
||||||
|
|
||||||
export type PersonChipPropsType = {
|
export type PersonChipPropsType = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
picture?: string;
|
picture?: string;
|
||||||
|
clickable?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseStyle = ({ theme }: { theme: Theme }) => `
|
export function PersonChip({
|
||||||
align-items: center;
|
id,
|
||||||
background-color: ${theme.background.tertiary};
|
name,
|
||||||
border-radius: ${theme.spacing(1)};
|
picture,
|
||||||
color: ${theme.font.color.primary};
|
clickable,
|
||||||
display: inline-flex;
|
}: PersonChipPropsType) {
|
||||||
gap: ${theme.spacing(1)};
|
|
||||||
height: 12px;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: ${theme.spacing(1)};
|
|
||||||
text-decoration: none;
|
|
||||||
:hover {
|
|
||||||
filter: brightness(95%);
|
|
||||||
}
|
|
||||||
img {
|
|
||||||
border-radius: ${theme.border.radius.rounded};
|
|
||||||
height: 14px;
|
|
||||||
object-fit: cover;
|
|
||||||
width: 14px;
|
|
||||||
}
|
|
||||||
white-space: nowrap;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledContainerLink = styled(Link)`
|
|
||||||
${baseStyle}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledContainerNoLink = styled.div`
|
|
||||||
${baseStyle}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledName = styled.span`
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export function PersonChip({ id, name, picture }: PersonChipPropsType) {
|
|
||||||
const ContainerComponent = id ? StyledContainerLink : StyledContainerNoLink;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContainerComponent data-testid="person-chip" to={`/person/${id}`}>
|
<EntityChip
|
||||||
<Avatar
|
entityId={id}
|
||||||
avatarUrl={picture}
|
linkToEntity={`/person/${id}`}
|
||||||
colorId={id}
|
name={name}
|
||||||
placeholder={name}
|
avatarType="rounded"
|
||||||
size={14}
|
clickable={clickable}
|
||||||
type="rounded"
|
picture={picture}
|
||||||
/>
|
/>
|
||||||
<StyledName>{name}</StyledName>
|
|
||||||
</ContainerComponent>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
97
front/src/modules/ui/chip/components/EntityChip.tsx
Normal file
97
front/src/modules/ui/chip/components/EntityChip.tsx
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { Theme } from '@emotion/react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { Avatar, AvatarType } from '@/users/components/Avatar';
|
||||||
|
|
||||||
|
const baseStyle = ({ theme }: { theme: Theme }) => `
|
||||||
|
align-items: center;
|
||||||
|
border-radius: ${theme.spacing(1)};
|
||||||
|
color: ${theme.font.color.primary};
|
||||||
|
display: inline-flex;
|
||||||
|
gap: ${theme.spacing(1)};
|
||||||
|
height: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: ${theme.spacing(1)};
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: ${theme.border.radius.rounded};
|
||||||
|
height: 14px;
|
||||||
|
object-fit: cover;
|
||||||
|
width: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledContainerLink = styled.div`
|
||||||
|
${baseStyle}
|
||||||
|
|
||||||
|
background-color: ${(props) => props.theme.background.tertiary};
|
||||||
|
:hover {
|
||||||
|
filter: brightness(95%);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledContainerReadOnly = styled.div`
|
||||||
|
${baseStyle}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledName = styled.span`
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
linkToEntity: string;
|
||||||
|
entityId: string;
|
||||||
|
name: string;
|
||||||
|
picture?: string;
|
||||||
|
clickable?: boolean;
|
||||||
|
avatarType?: AvatarType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function EntityChip({
|
||||||
|
linkToEntity,
|
||||||
|
entityId,
|
||||||
|
name,
|
||||||
|
picture,
|
||||||
|
clickable,
|
||||||
|
avatarType = 'rounded',
|
||||||
|
}: OwnProps) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
function handleLinkClick(event: React.MouseEvent<HTMLDivElement>) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
navigate(linkToEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clickable && linkToEntity ? (
|
||||||
|
<StyledContainerLink data-testid="entity-chip" onClick={handleLinkClick}>
|
||||||
|
<Avatar
|
||||||
|
avatarUrl={picture}
|
||||||
|
colorId={entityId}
|
||||||
|
placeholder={name}
|
||||||
|
size={14}
|
||||||
|
type={avatarType}
|
||||||
|
/>
|
||||||
|
<StyledName>{name}</StyledName>
|
||||||
|
</StyledContainerLink>
|
||||||
|
) : (
|
||||||
|
<StyledContainerReadOnly data-testid="entity-chip">
|
||||||
|
<Avatar
|
||||||
|
avatarUrl={picture}
|
||||||
|
colorId={entityId}
|
||||||
|
placeholder={name}
|
||||||
|
size={14}
|
||||||
|
type={avatarType}
|
||||||
|
/>
|
||||||
|
<StyledName>{name}</StyledName>
|
||||||
|
</StyledContainerReadOnly>
|
||||||
|
);
|
||||||
|
}
|
@ -4,6 +4,7 @@ import { motion } from 'framer-motion';
|
|||||||
|
|
||||||
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
|
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
|
||||||
|
|
||||||
|
import { useBindFieldHotkeyScope } from '../hooks/useBindFieldHotkeyScope';
|
||||||
import { useEditableField } from '../hooks/useEditableField';
|
import { useEditableField } from '../hooks/useEditableField';
|
||||||
|
|
||||||
import { EditableFieldDisplayMode } from './EditableFieldDisplayMode';
|
import { EditableFieldDisplayMode } from './EditableFieldDisplayMode';
|
||||||
@ -43,8 +44,10 @@ export const EditableFieldBaseContainer = styled.div`
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
gap: ${({ theme }) => theme.spacing(1)};
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
height: 24px;
|
height: 24px;
|
||||||
|
justify-content: flex-start;
|
||||||
position: relative;
|
position: relative;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
@ -60,6 +63,7 @@ type OwnProps = {
|
|||||||
displayModeContent: React.ReactNode;
|
displayModeContent: React.ReactNode;
|
||||||
parentHotkeyScope?: HotkeyScope;
|
parentHotkeyScope?: HotkeyScope;
|
||||||
customEditHotkeyScope?: HotkeyScope;
|
customEditHotkeyScope?: HotkeyScope;
|
||||||
|
isDisplayModeContentEmpty?: boolean;
|
||||||
onSubmit?: () => void;
|
onSubmit?: () => void;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
};
|
};
|
||||||
@ -73,11 +77,17 @@ export function EditableField({
|
|||||||
displayModeContent,
|
displayModeContent,
|
||||||
parentHotkeyScope,
|
parentHotkeyScope,
|
||||||
customEditHotkeyScope,
|
customEditHotkeyScope,
|
||||||
|
isDisplayModeContentEmpty,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
|
||||||
|
useBindFieldHotkeyScope({
|
||||||
|
customEditHotkeyScope,
|
||||||
|
parentHotkeyScope,
|
||||||
|
});
|
||||||
|
|
||||||
function handleContainerMouseEnter() {
|
function handleContainerMouseEnter() {
|
||||||
setIsHovered(true);
|
setIsHovered(true);
|
||||||
}
|
}
|
||||||
@ -86,11 +96,10 @@ export function EditableField({
|
|||||||
setIsHovered(false);
|
setIsHovered(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isFieldInEditMode, openEditableField } =
|
const { isFieldInEditMode, openEditableField } = useEditableField();
|
||||||
useEditableField(parentHotkeyScope);
|
|
||||||
|
|
||||||
function handleDisplayModeClick() {
|
function handleDisplayModeClick() {
|
||||||
openEditableField(customEditHotkeyScope);
|
openEditableField();
|
||||||
}
|
}
|
||||||
|
|
||||||
const showEditButton = !isFieldInEditMode && isHovered && useEditButton;
|
const showEditButton = !isFieldInEditMode && isHovered && useEditButton;
|
||||||
@ -114,6 +123,7 @@ export function EditableField({
|
|||||||
<EditableFieldDisplayMode
|
<EditableFieldDisplayMode
|
||||||
disableClick={useEditButton}
|
disableClick={useEditButton}
|
||||||
onClick={handleDisplayModeClick}
|
onClick={handleDisplayModeClick}
|
||||||
|
isDisplayModeContentEmpty={isDisplayModeContentEmpty}
|
||||||
>
|
>
|
||||||
{displayModeContent}
|
{displayModeContent}
|
||||||
</EditableFieldDisplayMode>
|
</EditableFieldDisplayMode>
|
||||||
|
@ -2,7 +2,7 @@ import { css } from '@emotion/react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
export const EditableFieldNormalModeOuterContainer = styled.div<
|
export const EditableFieldNormalModeOuterContainer = styled.div<
|
||||||
Pick<OwnProps, 'disableClick'>
|
Pick<OwnProps, 'disableClick' | 'isDisplayModeContentEmpty'>
|
||||||
>`
|
>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
@ -15,7 +15,18 @@ export const EditableFieldNormalModeOuterContainer = styled.div<
|
|||||||
|
|
||||||
padding: ${({ theme }) => theme.spacing(1)};
|
padding: ${({ theme }) => theme.spacing(1)};
|
||||||
|
|
||||||
width: 100%;
|
${(props) => {
|
||||||
|
console.log(props.isDisplayModeContentEmpty);
|
||||||
|
if (props.isDisplayModeContentEmpty) {
|
||||||
|
return css`
|
||||||
|
min-width: 50px;
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
return css`
|
||||||
|
width: fit-content;
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
|
||||||
${(props) => {
|
${(props) => {
|
||||||
if (props.disableClick) {
|
if (props.disableClick) {
|
||||||
@ -51,17 +62,20 @@ export const EditableFieldNormalModeInnerContainer = styled.div`
|
|||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
disableClick?: boolean;
|
disableClick?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
isDisplayModeContentEmpty?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EditableFieldDisplayMode({
|
export function EditableFieldDisplayMode({
|
||||||
children,
|
children,
|
||||||
disableClick,
|
disableClick,
|
||||||
onClick,
|
onClick,
|
||||||
|
isDisplayModeContentEmpty,
|
||||||
}: React.PropsWithChildren<OwnProps>) {
|
}: React.PropsWithChildren<OwnProps>) {
|
||||||
return (
|
return (
|
||||||
<EditableFieldNormalModeOuterContainer
|
<EditableFieldNormalModeOuterContainer
|
||||||
onClick={disableClick ? undefined : onClick}
|
onClick={disableClick ? undefined : onClick}
|
||||||
disableClick={disableClick}
|
disableClick={disableClick}
|
||||||
|
isDisplayModeContentEmpty={isDisplayModeContentEmpty}
|
||||||
>
|
>
|
||||||
<EditableFieldNormalModeInnerContainer>
|
<EditableFieldNormalModeInnerContainer>
|
||||||
{children}
|
{children}
|
||||||
|
@ -34,7 +34,7 @@ export function EditableFieldEditButton({ customHotkeyScope }: OwnProps) {
|
|||||||
const { openEditableField } = useEditableField();
|
const { openEditableField } = useEditableField();
|
||||||
|
|
||||||
function handleClick() {
|
function handleClick() {
|
||||||
openEditableField(customHotkeyScope);
|
openEditableField();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
|
||||||
|
import { isSameHotkeyScope } from '@/ui/hotkey/utils/isSameHotkeyScope';
|
||||||
|
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
|
||||||
|
|
||||||
|
import { customEditHotkeyScopeForFieldScopedState } from '../states/customEditHotkeyScopeForFieldScopedState';
|
||||||
|
import { FieldContext } from '../states/FieldContext';
|
||||||
|
import { parentHotkeyScopeForFieldScopedState } from '../states/parentHotkeyScopeForFieldScopedState';
|
||||||
|
|
||||||
|
export function useBindFieldHotkeyScope({
|
||||||
|
customEditHotkeyScope,
|
||||||
|
parentHotkeyScope,
|
||||||
|
}: {
|
||||||
|
customEditHotkeyScope?: HotkeyScope;
|
||||||
|
parentHotkeyScope?: HotkeyScope;
|
||||||
|
}) {
|
||||||
|
const [customEditHotkeyScopeForField, setCustomEditHotkeyScopeForField] =
|
||||||
|
useRecoilScopedState(
|
||||||
|
customEditHotkeyScopeForFieldScopedState,
|
||||||
|
FieldContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [parentHotkeyScopeForField, setParentHotkeyScopeForField] =
|
||||||
|
useRecoilScopedState(parentHotkeyScopeForFieldScopedState, FieldContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
customEditHotkeyScope &&
|
||||||
|
!isSameHotkeyScope(customEditHotkeyScope, customEditHotkeyScopeForField)
|
||||||
|
) {
|
||||||
|
setCustomEditHotkeyScopeForField(customEditHotkeyScope);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
customEditHotkeyScope,
|
||||||
|
customEditHotkeyScopeForField,
|
||||||
|
setCustomEditHotkeyScopeForField,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
parentHotkeyScope &&
|
||||||
|
!isSameHotkeyScope(parentHotkeyScope, parentHotkeyScopeForField)
|
||||||
|
) {
|
||||||
|
setParentHotkeyScopeForField(parentHotkeyScope);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
parentHotkeyScope,
|
||||||
|
parentHotkeyScopeForField,
|
||||||
|
setParentHotkeyScopeForField,
|
||||||
|
]);
|
||||||
|
}
|
@ -1,33 +1,50 @@
|
|||||||
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
|
import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
|
||||||
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
|
|
||||||
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
|
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
|
||||||
|
|
||||||
|
import { customEditHotkeyScopeForFieldScopedState } from '../states/customEditHotkeyScopeForFieldScopedState';
|
||||||
import { FieldContext } from '../states/FieldContext';
|
import { FieldContext } from '../states/FieldContext';
|
||||||
import { isFieldInEditModeScopedState } from '../states/isFieldInEditModeScopedState';
|
import { isFieldInEditModeScopedState } from '../states/isFieldInEditModeScopedState';
|
||||||
|
import { parentHotkeyScopeForFieldScopedState } from '../states/parentHotkeyScopeForFieldScopedState';
|
||||||
import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope';
|
import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope';
|
||||||
|
|
||||||
// TODO: use atoms for hotkey scopes
|
// TODO: use atoms for hotkey scopes
|
||||||
export function useEditableField(parentHotkeyScope?: HotkeyScope) {
|
export function useEditableField() {
|
||||||
const [isFieldInEditMode, setIsFieldInEditMode] = useRecoilScopedState(
|
const [isFieldInEditMode, setIsFieldInEditMode] = useRecoilScopedState(
|
||||||
isFieldInEditModeScopedState,
|
isFieldInEditModeScopedState,
|
||||||
FieldContext,
|
FieldContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [customEditHotkeyScopeForField] = useRecoilScopedState(
|
||||||
|
customEditHotkeyScopeForFieldScopedState,
|
||||||
|
FieldContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [parentHotkeyScopeForField] = useRecoilScopedState(
|
||||||
|
parentHotkeyScopeForFieldScopedState,
|
||||||
|
FieldContext,
|
||||||
|
);
|
||||||
|
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
|
|
||||||
function closeEditableField() {
|
function closeEditableField() {
|
||||||
setIsFieldInEditMode(false);
|
setIsFieldInEditMode(false);
|
||||||
|
|
||||||
if (parentHotkeyScope) {
|
if (parentHotkeyScopeForField) {
|
||||||
setHotkeyScope(parentHotkeyScope.scope, parentHotkeyScope.customScopes);
|
setHotkeyScope(
|
||||||
|
parentHotkeyScopeForField.scope,
|
||||||
|
parentHotkeyScopeForField.customScopes,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openEditableField(customHotkeyScope?: HotkeyScope) {
|
function openEditableField() {
|
||||||
setIsFieldInEditMode(true);
|
setIsFieldInEditMode(true);
|
||||||
|
|
||||||
if (customHotkeyScope) {
|
if (customEditHotkeyScopeForField) {
|
||||||
setHotkeyScope(customHotkeyScope.scope, customHotkeyScope.customScopes);
|
setHotkeyScope(
|
||||||
|
customEditHotkeyScopeForField.scope,
|
||||||
|
customEditHotkeyScopeForField.customScopes,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
setHotkeyScope(EditableFieldHotkeyScope.EditableField);
|
setHotkeyScope(EditableFieldHotkeyScope.EditableField);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
import { atomFamily } from 'recoil';
|
||||||
|
|
||||||
|
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
|
||||||
|
|
||||||
|
export const customEditHotkeyScopeForFieldScopedState = atomFamily<
|
||||||
|
HotkeyScope | null,
|
||||||
|
string
|
||||||
|
>({
|
||||||
|
key: 'customEditHotkeyScopeForFieldScopedState',
|
||||||
|
default: null,
|
||||||
|
});
|
@ -0,0 +1,11 @@
|
|||||||
|
import { atomFamily } from 'recoil';
|
||||||
|
|
||||||
|
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
|
||||||
|
|
||||||
|
export const parentHotkeyScopeForFieldScopedState = atomFamily<
|
||||||
|
HotkeyScope | null,
|
||||||
|
string
|
||||||
|
>({
|
||||||
|
key: 'parentHotkeyScopeForFieldScopedState',
|
||||||
|
default: null,
|
||||||
|
});
|
@ -10,12 +10,8 @@ type OwnProps = {
|
|||||||
parentHotkeyScope?: HotkeyScope;
|
parentHotkeyScope?: HotkeyScope;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EditableFieldEditModeDate({
|
export function EditableFieldEditModeDate({ value, onChange }: OwnProps) {
|
||||||
value,
|
const { closeEditableField } = useEditableField();
|
||||||
onChange,
|
|
||||||
parentHotkeyScope,
|
|
||||||
}: OwnProps) {
|
|
||||||
const { closeEditableField } = useEditableField(parentHotkeyScope);
|
|
||||||
|
|
||||||
function handleChange(newValue: string) {
|
function handleChange(newValue: string) {
|
||||||
onChange?.(newValue);
|
onChange?.(newValue);
|
||||||
|
8
front/src/modules/ui/hotkey/utils/isSameHotkeyScope.ts
Normal file
8
front/src/modules/ui/hotkey/utils/isSameHotkeyScope.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { HotkeyScope } from '../types/HotkeyScope';
|
||||||
|
|
||||||
|
export function isSameHotkeyScope(
|
||||||
|
hotkeyScope1: HotkeyScope | undefined | null,
|
||||||
|
hotkeyScope2: HotkeyScope | undefined | null,
|
||||||
|
): boolean {
|
||||||
|
return JSON.stringify(hotkeyScope1) === JSON.stringify(hotkeyScope2);
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
softFocus?: boolean;
|
||||||
|
onClick?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditableCellDisplayModeOuterContainer = styled.div<
|
||||||
|
Pick<Props, 'softFocus'>
|
||||||
|
>`
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||||
|
padding-right: ${({ theme }) => theme.spacing(1)};
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
${(props) =>
|
||||||
|
props.softFocus
|
||||||
|
? `background: ${props.theme.background.transparent.secondary};
|
||||||
|
border-radius: ${props.theme.border.radius.sm};
|
||||||
|
box-shadow: inset 0 0 0 1px ${props.theme.font.color.extraLight};`
|
||||||
|
: ''}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const EditableCellDisplayModeInnerContainer = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function EditableCellDisplayContainer({
|
||||||
|
children,
|
||||||
|
softFocus,
|
||||||
|
onClick,
|
||||||
|
}: React.PropsWithChildren<Props>) {
|
||||||
|
return (
|
||||||
|
<EditableCellDisplayModeOuterContainer
|
||||||
|
onClick={onClick}
|
||||||
|
softFocus={softFocus}
|
||||||
|
>
|
||||||
|
<EditableCellDisplayModeInnerContainer>
|
||||||
|
{children}
|
||||||
|
</EditableCellDisplayModeInnerContainer>
|
||||||
|
</EditableCellDisplayModeOuterContainer>
|
||||||
|
);
|
||||||
|
}
|
@ -1,51 +1,19 @@
|
|||||||
import styled from '@emotion/styled';
|
|
||||||
|
|
||||||
import { useSetSoftFocusOnCurrentCell } from '../hooks/useSetSoftFocusOnCurrentCell';
|
import { useSetSoftFocusOnCurrentCell } from '../hooks/useSetSoftFocusOnCurrentCell';
|
||||||
|
|
||||||
type Props = {
|
import { EditableCellDisplayContainer } from './EditableCellContainer';
|
||||||
softFocus?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EditableCellNormalModeOuterContainer = styled.div<Props>`
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
|
||||||
padding-right: ${({ theme }) => theme.spacing(1)};
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
${(props) =>
|
|
||||||
props.softFocus
|
|
||||||
? `background: ${props.theme.background.transparent.secondary};
|
|
||||||
border-radius: ${props.theme.border.radius.sm};
|
|
||||||
box-shadow: inset 0 0 0 1px ${props.theme.font.color.extraLight};`
|
|
||||||
: ''}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const EditableCellNormalModeInnerContainer = styled.div`
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export function EditableCellDisplayMode({
|
export function EditableCellDisplayMode({
|
||||||
children,
|
children,
|
||||||
}: React.PropsWithChildren<unknown>) {
|
}: React.PropsWithChildren<unknown>) {
|
||||||
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
|
const setSoftFocusOnCurrentCell = useSetSoftFocusOnCurrentCell();
|
||||||
|
|
||||||
function handleOnClick() {
|
function handleClick() {
|
||||||
setSoftFocusOnCurrentCell();
|
setSoftFocusOnCurrentCell();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCellNormalModeOuterContainer onClick={handleOnClick}>
|
<EditableCellDisplayContainer onClick={handleClick}>
|
||||||
<EditableCellNormalModeInnerContainer>
|
{children}
|
||||||
{children}
|
</EditableCellDisplayContainer>
|
||||||
</EditableCellNormalModeInnerContainer>
|
|
||||||
</EditableCellNormalModeOuterContainer>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import { PropsWithChildren } from 'react';
|
||||||
|
|
||||||
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
||||||
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
|
import { HotkeyScope } from '@/ui/hotkey/types/HotkeyScope';
|
||||||
@ -7,15 +7,16 @@ import { isNonTextWritingKey } from '@/ui/hotkey/utils/isNonTextWritingKey';
|
|||||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
import { useEditableCell } from '../hooks/useEditableCell';
|
import { useEditableCell } from '../hooks/useEditableCell';
|
||||||
|
|
||||||
import {
|
import { EditableCellDisplayContainer } from './EditableCellContainer';
|
||||||
EditableCellNormalModeInnerContainer,
|
|
||||||
EditableCellNormalModeOuterContainer,
|
type OwnProps = PropsWithChildren<{
|
||||||
} from './EditableCellDisplayMode';
|
editHotkeyScope?: HotkeyScope;
|
||||||
|
}>;
|
||||||
|
|
||||||
export function EditableCellSoftFocusMode({
|
export function EditableCellSoftFocusMode({
|
||||||
children,
|
children,
|
||||||
editHotkeyScope,
|
editHotkeyScope,
|
||||||
}: React.PropsWithChildren<{ editHotkeyScope?: HotkeyScope }>) {
|
}: OwnProps) {
|
||||||
const { openEditableCell } = useEditableCell();
|
const { openEditableCell } = useEditableCell();
|
||||||
|
|
||||||
function openEditMode() {
|
function openEditMode() {
|
||||||
@ -61,13 +62,8 @@ export function EditableCellSoftFocusMode({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCellNormalModeOuterContainer
|
<EditableCellDisplayContainer onClick={handleClick} softFocus>
|
||||||
onClick={handleClick}
|
{children}
|
||||||
softFocus={true}
|
</EditableCellDisplayContainer>
|
||||||
>
|
|
||||||
<EditableCellNormalModeInnerContainer>
|
|
||||||
{children}
|
|
||||||
</EditableCellNormalModeInnerContainer>
|
|
||||||
</EditableCellNormalModeOuterContainer>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
import {
|
import { ChangeEvent, ReactNode, useEffect, useRef, useState } from 'react';
|
||||||
ChangeEvent,
|
|
||||||
ComponentType,
|
|
||||||
ReactNode,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { textInputStyle } from '@/ui/themes/effects';
|
import { textInputStyle } from '@/ui/themes/effects';
|
||||||
@ -13,18 +6,11 @@ import { textInputStyle } from '@/ui/themes/effects';
|
|||||||
import { EditableCell } from '../components/EditableCell';
|
import { EditableCell } from '../components/EditableCell';
|
||||||
|
|
||||||
export type EditableChipProps = {
|
export type EditableChipProps = {
|
||||||
id: string;
|
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
value: string;
|
value: string;
|
||||||
picture: string;
|
|
||||||
changeHandler: (updated: string) => void;
|
changeHandler: (updated: string) => void;
|
||||||
editModeHorizontalAlign?: 'left' | 'right';
|
editModeHorizontalAlign?: 'left' | 'right';
|
||||||
ChipComponent: ComponentType<{
|
ChipComponent: React.ReactNode;
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
picture: string;
|
|
||||||
isOverlapped?: boolean;
|
|
||||||
}>;
|
|
||||||
commentThreadCount?: number;
|
commentThreadCount?: number;
|
||||||
onCommentClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
onCommentClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
rightEndContents?: ReactNode[];
|
rightEndContents?: ReactNode[];
|
||||||
@ -52,11 +38,9 @@ const RightContainer = styled.div`
|
|||||||
|
|
||||||
// TODO: move right end content in EditableCell
|
// TODO: move right end content in EditableCell
|
||||||
export function EditableCellChip({
|
export function EditableCellChip({
|
||||||
id,
|
|
||||||
value,
|
value,
|
||||||
placeholder,
|
placeholder,
|
||||||
changeHandler,
|
changeHandler,
|
||||||
picture,
|
|
||||||
editModeHorizontalAlign,
|
editModeHorizontalAlign,
|
||||||
ChipComponent,
|
ChipComponent,
|
||||||
rightEndContents,
|
rightEndContents,
|
||||||
@ -95,7 +79,7 @@ export function EditableCellChip({
|
|||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
nonEditModeContent={
|
nonEditModeContent={
|
||||||
<NoEditModeContainer>
|
<NoEditModeContainer>
|
||||||
<ChipComponent id={id} name={inputValue} picture={picture} />
|
{ChipComponent}
|
||||||
<RightContainer>
|
<RightContainer>
|
||||||
{rightEndContents &&
|
{rightEndContents &&
|
||||||
rightEndContents.length > 0 &&
|
rightEndContents.length > 0 &&
|
||||||
|
Loading…
Reference in New Issue
Block a user