* wip * wip * - Added scopes on useHotkeys - Use new EditableCellV2 - Implemented Recoil Scoped State with specific context - Implemented soft focus position - Factorized open/close editable cell - Removed editable relation old components - Broke down entity table into multiple components - Added Recoil Scope by CellContext - Added Recoil Scope by RowContext * First working version * Use a new EditableCellSoftFocusMode * Fixed initialize soft focus * Fixed enter mode * Added TODO * Fix * Fixes * Fix tests * Fix lint * Fixes --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
222 lines
5.7 KiB
TypeScript
222 lines
5.7 KiB
TypeScript
import { ReactNode, useRef } from 'react';
|
|
import styled from '@emotion/styled';
|
|
import { useRecoilState } from 'recoil';
|
|
|
|
import { captureHotkeyTypeInFocusState } from '@/hotkeys/states/captureHotkeyTypeInFocusState';
|
|
import { IconChevronDown } from '@/ui/icons/index';
|
|
import { overlayBackground, textInputStyle } from '@/ui/themes/effects';
|
|
|
|
import { useOutsideAlerter } from '../../../hooks/useOutsideAlerter';
|
|
|
|
type OwnProps = {
|
|
label: string;
|
|
isActive: boolean;
|
|
children?: ReactNode;
|
|
isUnfolded?: boolean;
|
|
setIsUnfolded?: React.Dispatch<React.SetStateAction<boolean>>;
|
|
resetState?: () => void;
|
|
};
|
|
|
|
const StyledDropdownButtonContainer = styled.div`
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: relative;
|
|
z-index: 1;
|
|
`;
|
|
|
|
type StyledDropdownButtonProps = {
|
|
isUnfolded: boolean;
|
|
isActive: boolean;
|
|
};
|
|
|
|
const StyledDropdownButton = styled.div<StyledDropdownButtonProps>`
|
|
background: ${({ theme }) => theme.background.primary};
|
|
border-radius: 4px;
|
|
color: ${(props) => (props.isActive ? props.theme.color.blue : 'none')};
|
|
cursor: pointer;
|
|
display: flex;
|
|
filter: ${(props) => (props.isUnfolded ? 'brightness(0.95)' : 'none')};
|
|
|
|
padding: ${({ theme }) => theme.spacing(1)};
|
|
padding-left: ${({ theme }) => theme.spacing(2)};
|
|
|
|
padding-right: ${({ theme }) => theme.spacing(2)};
|
|
user-select: none;
|
|
|
|
&:hover {
|
|
filter: brightness(0.95);
|
|
}
|
|
`;
|
|
|
|
const StyledDropdown = styled.ul`
|
|
--outer-border-radius: calc(var(--wraper-border-radius) - 2px);
|
|
--wraper-border: 1px;
|
|
--wraper-border-radius: 8px;
|
|
|
|
border: var(--wraper-border) solid ${({ theme }) => theme.border.color.light};
|
|
border-radius: var(--wraper-border-radius);
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 160px;
|
|
padding: 0px;
|
|
position: absolute;
|
|
right: 0;
|
|
top: 14px;
|
|
${overlayBackground}
|
|
li {
|
|
&:first-of-type {
|
|
border-top-left-radius: var(--outer-border-radius);
|
|
border-top-right-radius: var(--outer-border-radius);
|
|
}
|
|
&:last-of-type {
|
|
border-bottom: 0;
|
|
border-bottom-left-radius: var(--outer-border-radius);
|
|
border-bottom-right-radius: var(--outer-border-radius);
|
|
}
|
|
}
|
|
`;
|
|
|
|
const StyledDropdownItem = styled.li`
|
|
align-items: center;
|
|
border-radius: 2px;
|
|
color: ${({ theme }) => theme.font.color.secondary};
|
|
cursor: pointer;
|
|
display: flex;
|
|
margin: 2px;
|
|
padding: ${({ theme }) => theme.spacing(2)}
|
|
calc(${({ theme }) => theme.spacing(2)} - 2px);
|
|
user-select: none;
|
|
width: calc(160px - ${({ theme }) => theme.spacing(4)});
|
|
|
|
&:hover {
|
|
background: ${({ theme }) => theme.background.transparent.light};
|
|
}
|
|
`;
|
|
|
|
const StyledDropdownItemClipped = styled.span`
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
`;
|
|
|
|
const StyledDropdownTopOption = styled.li`
|
|
align-items: center;
|
|
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
|
color: ${({ theme }) => theme.font.color.primary};
|
|
cursor: pointer;
|
|
display: flex;
|
|
font-size: ${({ theme }) => theme.font.size.sm};
|
|
font-weight: ${({ theme }) => theme.font.weight.medium};
|
|
justify-content: space-between;
|
|
padding: calc(${({ theme }) => theme.spacing(2)})
|
|
calc(${({ theme }) => theme.spacing(2)});
|
|
|
|
&:hover {
|
|
background: ${({ theme }) => theme.background.transparent.light};
|
|
}
|
|
user-select: none;
|
|
`;
|
|
|
|
const StyledIcon = styled.div`
|
|
display: flex;
|
|
justify-content: center;
|
|
margin-right: ${({ theme }) => theme.spacing(1)};
|
|
min-width: ${({ theme }) => theme.spacing(4)};
|
|
`;
|
|
|
|
const StyledSearchField = styled.li`
|
|
align-items: center;
|
|
border-bottom: var(--wraper-border) solid
|
|
${({ theme }) => theme.border.color.light};
|
|
color: ${({ theme }) => theme.font.color.secondary};
|
|
|
|
cursor: pointer;
|
|
display: flex;
|
|
font-weight: ${({ theme }) => theme.font.weight.medium};
|
|
justify-content: space-between;
|
|
overflow: hidden;
|
|
|
|
user-select: none;
|
|
input {
|
|
border-radius: 8px;
|
|
box-sizing: border-box;
|
|
font-family: ${({ theme }) => theme.font.family};
|
|
height: 36px;
|
|
padding: 8px;
|
|
width: 100%;
|
|
|
|
${textInputStyle}
|
|
|
|
&:focus {
|
|
outline: 0 none;
|
|
}
|
|
}
|
|
`;
|
|
|
|
function DropdownButton({
|
|
label,
|
|
isActive,
|
|
children,
|
|
isUnfolded = false,
|
|
setIsUnfolded,
|
|
resetState,
|
|
}: OwnProps) {
|
|
const [, setCaptureHotkeyTypeInFocus] = useRecoilState(
|
|
captureHotkeyTypeInFocusState,
|
|
);
|
|
|
|
const onButtonClick = () => {
|
|
setIsUnfolded && setIsUnfolded(!isUnfolded);
|
|
setCaptureHotkeyTypeInFocus(!isUnfolded);
|
|
};
|
|
|
|
const onOutsideClick = () => {
|
|
setCaptureHotkeyTypeInFocus(false);
|
|
setIsUnfolded && setIsUnfolded(false);
|
|
resetState && resetState();
|
|
};
|
|
|
|
const dropdownRef = useRef(null);
|
|
useOutsideAlerter(dropdownRef, onOutsideClick);
|
|
|
|
return (
|
|
<StyledDropdownButtonContainer>
|
|
<StyledDropdownButton
|
|
isUnfolded={isUnfolded}
|
|
onClick={onButtonClick}
|
|
isActive={isActive}
|
|
aria-selected={isActive}
|
|
>
|
|
{label}
|
|
</StyledDropdownButton>
|
|
{isUnfolded && (
|
|
<StyledDropdown ref={dropdownRef}>{children}</StyledDropdown>
|
|
)}
|
|
</StyledDropdownButtonContainer>
|
|
);
|
|
}
|
|
|
|
const StyleAngleDownContainer = styled.div`
|
|
color: ${({ theme }) => theme.font.color.tertiary};
|
|
display: flex;
|
|
height: 100%;
|
|
justify-content: center;
|
|
margin-left: auto;
|
|
`;
|
|
|
|
function DropdownTopOptionAngleDown() {
|
|
return (
|
|
<StyleAngleDownContainer>
|
|
<IconChevronDown size={16} />
|
|
</StyleAngleDownContainer>
|
|
);
|
|
}
|
|
DropdownButton.StyledDropdownItem = StyledDropdownItem;
|
|
DropdownButton.StyledDropdownItemClipped = StyledDropdownItemClipped;
|
|
DropdownButton.StyledSearchField = StyledSearchField;
|
|
DropdownButton.StyledDropdownTopOption = StyledDropdownTopOption;
|
|
DropdownButton.StyledDropdownTopOptionAngleDown = DropdownTopOptionAngleDown;
|
|
DropdownButton.StyledIcon = StyledIcon;
|
|
|
|
export default DropdownButton;
|