mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-02 14:49:44 +08:00
init: the first public commit for AFFiNE
This commit is contained in:
+47
@@ -0,0 +1,47 @@
|
||||
import React, { CSSProperties } from 'react';
|
||||
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
|
||||
import { iOS } from '../utils';
|
||||
import { TreeItem, TreeItemProps } from './TreeItem';
|
||||
|
||||
type DndTreeItemProps = TreeItemProps & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export function DndTreeItem({ id, depth, ...props }: DndTreeItemProps) {
|
||||
const {
|
||||
attributes,
|
||||
isDragging,
|
||||
isSorting,
|
||||
listeners,
|
||||
setDraggableNodeRef,
|
||||
setDroppableNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
} = useSortable({ id });
|
||||
|
||||
const style: CSSProperties = {
|
||||
transform: CSS.Translate.toString(transform),
|
||||
transition,
|
||||
};
|
||||
|
||||
return (
|
||||
<TreeItem
|
||||
ref={setDraggableNodeRef}
|
||||
wrapperRef={setDroppableNodeRef}
|
||||
pageId={id}
|
||||
style={style}
|
||||
depth={depth}
|
||||
ghost={isDragging}
|
||||
disableSelection={iOS}
|
||||
disableInteraction={isSorting}
|
||||
handleProps={{
|
||||
...attributes,
|
||||
...listeners,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
import styles from './tree-item.module.scss';
|
||||
import {
|
||||
MuiSnackbar as Snackbar,
|
||||
Cascader,
|
||||
CascaderItemProps,
|
||||
MuiDivider as Divider,
|
||||
} from '@toeverything/components/ui';
|
||||
import React from 'react';
|
||||
import { NavLink, useNavigate } from 'react-router-dom';
|
||||
import { copyToClipboard } from '@toeverything/utils';
|
||||
import { services, TemplateFactory } from '@toeverything/datasource/db-service';
|
||||
import { NewFromTemplatePortal } from './NewFromTemplatePortal';
|
||||
import { useFlag } from '@toeverything/datasource/feature-flags';
|
||||
const MESSAGES = {
|
||||
COPY_LINK_SUCCESS: 'Copyed link to clipboard',
|
||||
};
|
||||
interface ActionsProps {
|
||||
workspaceId: string;
|
||||
pageId: string;
|
||||
onRemove: () => void;
|
||||
}
|
||||
function DndTreeItemMoreActions(props: ActionsProps) {
|
||||
const [alert_open, set_alert_open] = React.useState(false);
|
||||
const [anchorEl, setAnchorEl] = React.useState<HTMLDivElement | null>(null);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const workspaceId = props.workspaceId;
|
||||
const pageId = props.pageId;
|
||||
const blockUrl = window.location.origin + `/${workspaceId}/${pageId}`;
|
||||
|
||||
const redirect_to_page = (new_workspaceId: string, newPageId: string) => {
|
||||
navigate('/' + new_workspaceId + '/' + newPageId);
|
||||
};
|
||||
|
||||
const handle_alert_close = () => {
|
||||
set_alert_open(false);
|
||||
};
|
||||
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const open = Boolean(anchorEl);
|
||||
const handle_copy_link = () => {
|
||||
copyToClipboard(blockUrl);
|
||||
set_alert_open(true);
|
||||
handleClose();
|
||||
};
|
||||
const handle_delete = () => {
|
||||
props.onRemove();
|
||||
handleClose();
|
||||
};
|
||||
const handle_new_child_page = async () => {
|
||||
const new_page = await services.api.editorBlock.create({
|
||||
workspace: workspaceId,
|
||||
type: 'page' as const,
|
||||
});
|
||||
await services.api.pageTree.addChildPageToWorkspace(
|
||||
workspaceId,
|
||||
pageId,
|
||||
new_page.id
|
||||
);
|
||||
redirect_to_page(workspaceId, new_page.id);
|
||||
|
||||
handleClose();
|
||||
};
|
||||
const handle_new_prev_page = async () => {
|
||||
const new_page = await services.api.editorBlock.create({
|
||||
workspace: workspaceId,
|
||||
type: 'page' as const,
|
||||
});
|
||||
await services.api.pageTree.addPrevPageToWorkspace(
|
||||
workspaceId,
|
||||
pageId,
|
||||
new_page.id
|
||||
);
|
||||
|
||||
redirect_to_page(workspaceId, new_page.id);
|
||||
|
||||
handleClose();
|
||||
};
|
||||
const handle_new_next_page = async () => {
|
||||
const new_page = await services.api.editorBlock.create({
|
||||
workspace: workspaceId,
|
||||
type: 'page' as const,
|
||||
});
|
||||
await services.api.pageTree.addNextPageToWorkspace(
|
||||
workspaceId,
|
||||
pageId,
|
||||
new_page.id
|
||||
);
|
||||
|
||||
redirect_to_page(workspaceId, new_page.id);
|
||||
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handle_duplicate_page = async () => {
|
||||
//create page
|
||||
const new_page = await services.api.editorBlock.create({
|
||||
workspace: workspaceId,
|
||||
type: 'page' as const,
|
||||
});
|
||||
//add page to tree
|
||||
await services.api.pageTree.addNextPageToWorkspace(
|
||||
workspaceId,
|
||||
pageId,
|
||||
new_page.id
|
||||
);
|
||||
//copy source page to new page
|
||||
await services.api.editorBlock.copyPage(
|
||||
workspaceId,
|
||||
pageId,
|
||||
new_page.id
|
||||
);
|
||||
|
||||
redirect_to_page(workspaceId, new_page.id);
|
||||
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const redirectToPage = (newWorkspaceId: string, newPageId: string) => {
|
||||
navigate('/' + newWorkspaceId + '/' + newPageId);
|
||||
};
|
||||
|
||||
const handleNewFromTemplate = async template => {
|
||||
const newPage = await services.api.editorBlock.create({
|
||||
workspace: workspaceId,
|
||||
type: 'page' as const,
|
||||
});
|
||||
|
||||
await services.api.pageTree.addNextPageToWorkspace(
|
||||
workspaceId,
|
||||
pageId,
|
||||
newPage.id
|
||||
);
|
||||
|
||||
await services.api.editorBlock.copyTemplateToPage(
|
||||
workspaceId,
|
||||
newPage.id,
|
||||
TemplateFactory.generatePageTemplateByGroupKeys({
|
||||
name: template.name,
|
||||
groupKeys: template.groupKeys,
|
||||
})
|
||||
);
|
||||
|
||||
redirectToPage(workspaceId, newPage.id);
|
||||
};
|
||||
|
||||
const templateList = useFlag(
|
||||
'JSONTemplateList',
|
||||
TemplateFactory.defaultTemplateList
|
||||
);
|
||||
|
||||
const templateMenuList: CascaderItemProps[] = templateList.map(
|
||||
(template, index) => {
|
||||
const item = {
|
||||
title: template.name,
|
||||
callback: () => {
|
||||
handleNewFromTemplate(template);
|
||||
},
|
||||
};
|
||||
return item;
|
||||
}
|
||||
);
|
||||
|
||||
const menuList = [
|
||||
{
|
||||
title: 'Duplicate Page',
|
||||
callback: () => {
|
||||
handle_duplicate_page();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
isDivide: true,
|
||||
},
|
||||
{
|
||||
title: 'New Child Page',
|
||||
callback: () => {
|
||||
handle_new_child_page();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'New Prev Page',
|
||||
callback: () => {
|
||||
handle_new_prev_page();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'New Next Page',
|
||||
callback: () => {
|
||||
handle_new_next_page();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'New From Template',
|
||||
subItems: templateMenuList,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
isDivide: true,
|
||||
},
|
||||
{
|
||||
title: 'Open In New Tab',
|
||||
callback: () => {
|
||||
const new_window = window.open(
|
||||
`/${workspaceId}/${pageId}`,
|
||||
'_blank'
|
||||
);
|
||||
if (new_window) {
|
||||
new_window.focus();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
isDivide: true,
|
||||
},
|
||||
{
|
||||
title: 'Copy Link',
|
||||
callback: () => {
|
||||
handle_copy_link();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Delete',
|
||||
callback: () => {
|
||||
handle_delete();
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={styles['TreeItemMoreActions']}
|
||||
onClick={handleClick}
|
||||
>
|
||||
···
|
||||
</span>
|
||||
<Cascader
|
||||
items={menuList}
|
||||
anchorEl={anchorEl}
|
||||
placement="right-start"
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
></Cascader>
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||
open={alert_open}
|
||||
message={MESSAGES.COPY_LINK_SUCCESS}
|
||||
key={'bottomcenter'}
|
||||
autoHideDuration={2000}
|
||||
onClose={handle_alert_close}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default DndTreeItemMoreActions;
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
import React, {
|
||||
useState,
|
||||
MouseEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { services, TemplateFactory } from '@toeverything/datasource/db-service';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { copyToClipboard } from '@toeverything/utils';
|
||||
import { ViewSidebarIcon } from '@toeverything/components/common';
|
||||
import {
|
||||
MuiSnackbar as Snackbar,
|
||||
MuiPopover as Popover,
|
||||
ListButton,
|
||||
MuiDivider as Divider,
|
||||
MuiSwitch as Switch,
|
||||
styled,
|
||||
BaseButton,
|
||||
} from '@toeverything/components/ui';
|
||||
import { useUserAndSpaces } from '@toeverything/datasource/state';
|
||||
import { useFlag } from '@toeverything/datasource/feature-flags';
|
||||
const NewFromTemplatePortalContainer = styled('div')({
|
||||
width: '320p',
|
||||
padding: '15px',
|
||||
'.textDescription': {
|
||||
height: '22px',
|
||||
lineHeight: '22px',
|
||||
marginLeft: '30px',
|
||||
fontSize: '14px',
|
||||
color: '#ccc',
|
||||
p: {
|
||||
margin: 0,
|
||||
},
|
||||
},
|
||||
'.switchDescription': {
|
||||
color: '#ccc',
|
||||
fontSize: '14px',
|
||||
paddingLeft: '30px',
|
||||
},
|
||||
});
|
||||
|
||||
const BtnPageSettingContainer = styled('div')({
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
});
|
||||
|
||||
const MESSAGES = {
|
||||
COPY_LINK: ' Copy Link',
|
||||
INVITE: 'Add people,emails, or groups',
|
||||
COPY_LINK_SUCCESS: 'Copyed link to clipboard',
|
||||
};
|
||||
|
||||
interface NewFromTemplatePortalProps {
|
||||
workspaceId: string;
|
||||
pageId: string;
|
||||
}
|
||||
function NewFromTemplatePortal(props: NewFromTemplatePortalProps) {
|
||||
const [alertOpen, setAlertOpen] = useState(false);
|
||||
const [anchorEl, setAnchorEl] = React.useState<HTMLDivElement | null>(null);
|
||||
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { user } = useUserAndSpaces();
|
||||
|
||||
const workspaceId = props.workspaceId;
|
||||
const pageId = props.pageId;
|
||||
const handleAlertClose = () => {
|
||||
setAlertOpen(false);
|
||||
};
|
||||
const redirectToPage = (newWorkspaceId: string, newPageId: string) => {
|
||||
navigate('/' + newWorkspaceId + '/' + newPageId);
|
||||
};
|
||||
|
||||
const handleNewFromTemplate = async template => {
|
||||
const newPage = await services.api.editorBlock.create({
|
||||
workspace: workspaceId,
|
||||
type: 'page' as const,
|
||||
});
|
||||
|
||||
await services.api.pageTree.addNextPageToWorkspace(
|
||||
workspaceId,
|
||||
pageId,
|
||||
newPage.id
|
||||
);
|
||||
|
||||
await services.api.editorBlock.copyTemplateToPage(
|
||||
workspaceId,
|
||||
newPage.id,
|
||||
TemplateFactory.generatePageTemplateByGroupKeys({
|
||||
name: template.name,
|
||||
groupKeys: template.groupKeys,
|
||||
})
|
||||
);
|
||||
|
||||
redirectToPage(workspaceId, newPage.id);
|
||||
|
||||
handleClose();
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const newFromTemplateRef = useRef();
|
||||
const templateList = useFlag(
|
||||
'JSONTemplateList',
|
||||
TemplateFactory.defaultTemplateList
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div ref={newFromTemplateRef} onClick={handleClick}>
|
||||
<ListButton content="New From Template" onClick={() => {}} />
|
||||
</div>
|
||||
|
||||
<Popover
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<NewFromTemplatePortalContainer>
|
||||
<>
|
||||
{templateList.map((template, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => {
|
||||
handleNewFromTemplate(template);
|
||||
}}
|
||||
>
|
||||
<BaseButton>{template.name}</BaseButton>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
</NewFromTemplatePortalContainer>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export { NewFromTemplatePortal };
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
import React, {
|
||||
forwardRef,
|
||||
type CSSProperties,
|
||||
type HTMLAttributes,
|
||||
} from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import cx from 'clsx';
|
||||
import { CloseIcon, DocumentIcon } from '@toeverything/components/common';
|
||||
import {
|
||||
ArrowDropDownIcon,
|
||||
ArrowRightIcon,
|
||||
} from '@toeverything/components/icons';
|
||||
|
||||
import styles from './tree-item.module.scss';
|
||||
import { useFlag } from '@toeverything/datasource/feature-flags';
|
||||
|
||||
import MoreActions from './MoreActions';
|
||||
export type TreeItemProps = {
|
||||
/** The main text to display on this line */
|
||||
value: string;
|
||||
/** The layer number of the node, 0, 1, 2 */
|
||||
depth: number;
|
||||
/** The item in the DragOverlay is clone, the one in the normal list is not clone, and the delete icon is displayed through the clone control */
|
||||
clone?: boolean;
|
||||
pageId?: string;
|
||||
childCount?: number;
|
||||
collapsed?: boolean;
|
||||
disableInteraction?: boolean;
|
||||
disableSelection?: boolean;
|
||||
/** isDragging */
|
||||
ghost?: boolean;
|
||||
handleProps?: any;
|
||||
indicator?: boolean;
|
||||
indentationWidth: number;
|
||||
onCollapse?(): void;
|
||||
onRemove?(): void;
|
||||
/** The ref of the outermost container is often used as droppaHTMLAttributes<HTMLLIElement>ble-node; the ref of the inner dom is often used as draggable-node */
|
||||
wrapperRef?(node: HTMLLIElement): void;
|
||||
} & HTMLAttributes<HTMLLIElement>;
|
||||
|
||||
export const TreeItem = forwardRef<HTMLDivElement, TreeItemProps>(
|
||||
(
|
||||
{
|
||||
childCount,
|
||||
clone,
|
||||
depth,
|
||||
disableSelection,
|
||||
disableInteraction,
|
||||
ghost,
|
||||
handleProps,
|
||||
indentationWidth,
|
||||
indicator,
|
||||
collapsed,
|
||||
onCollapse,
|
||||
onRemove,
|
||||
style,
|
||||
value,
|
||||
wrapperRef,
|
||||
pageId,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { workspace_id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const BooleanPageTreeItemMoreActions = useFlag(
|
||||
'BooleanPageTreeItemMoreActions',
|
||||
false
|
||||
);
|
||||
return (
|
||||
<li
|
||||
ref={wrapperRef}
|
||||
className={cx(
|
||||
styles['Wrapper'],
|
||||
clone && styles['clone'],
|
||||
ghost && styles['ghost'],
|
||||
indicator && styles['indicator'],
|
||||
disableSelection && styles['disableSelection'],
|
||||
disableInteraction && styles['disableInteraction']
|
||||
)}
|
||||
style={
|
||||
{
|
||||
'--spacing': `${indentationWidth * depth}px`,
|
||||
} as CSSProperties
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
ref={ref}
|
||||
className={styles['TreeItem']}
|
||||
style={style}
|
||||
title={value}
|
||||
>
|
||||
<Action onClick={onCollapse}>
|
||||
{collapsed ? <ArrowRightIcon /> : <ArrowDropDownIcon />}
|
||||
</Action>
|
||||
<Action>
|
||||
<DocumentIcon />
|
||||
</Action>
|
||||
<span
|
||||
className={styles['Text']}
|
||||
{...handleProps}
|
||||
onClick={() => {
|
||||
navigate(`/${workspace_id}/${pageId}`);
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</span>
|
||||
{BooleanPageTreeItemMoreActions && (
|
||||
<MoreActions
|
||||
workspaceId={workspace_id}
|
||||
pageId={pageId}
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!clone && onRemove && <Remove onClick={onRemove} />}
|
||||
{clone && childCount && childCount > 1 ? (
|
||||
<span className={styles['Count']}>{childCount}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export interface ActionProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
active?: {
|
||||
fill: string;
|
||||
background: string;
|
||||
};
|
||||
// cursor?: CSSProperties['cursor'];
|
||||
cursor?: 'pointer' | 'grab';
|
||||
}
|
||||
|
||||
/** Customizable buttons */
|
||||
export function Action({
|
||||
active,
|
||||
className,
|
||||
cursor,
|
||||
style,
|
||||
...props
|
||||
}: ActionProps) {
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
className={cx(styles['Action'], className)}
|
||||
tabIndex={0}
|
||||
style={
|
||||
{
|
||||
...style,
|
||||
// cursor,
|
||||
'--fill': active?.fill,
|
||||
'--background': active?.background,
|
||||
} as CSSProperties
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function Handle(props: ActionProps) {
|
||||
return (
|
||||
<Action cursor="grab" data-cypress="draggable-handle" {...props}>
|
||||
<ArrowDropDownIcon />
|
||||
</Action>
|
||||
);
|
||||
}
|
||||
|
||||
export function Remove(props: ActionProps) {
|
||||
return (
|
||||
<Action
|
||||
{...props}
|
||||
active={{
|
||||
fill: 'rgba(255, 70, 70, 0.95)',
|
||||
background: 'rgba(255, 70, 70, 0.1)',
|
||||
}}
|
||||
>
|
||||
<CloseIcon style={{ fontSize: 12 }} />
|
||||
{/* <svg width="8" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.99998 -0.000206962C2.7441 -0.000206962 2.48794 0.0972617 2.29294 0.292762L0.292945 2.29276C-0.0980552 2.68376 -0.0980552 3.31682 0.292945 3.70682L7.58591 10.9998L0.292945 18.2928C-0.0980552 18.6838 -0.0980552 19.3168 0.292945 19.7068L2.29294 21.7068C2.68394 22.0978 3.31701 22.0978 3.70701 21.7068L11 14.4139L18.2929 21.7068C18.6829 22.0978 19.317 22.0978 19.707 21.7068L21.707 19.7068C22.098 19.3158 22.098 18.6828 21.707 18.2928L14.414 10.9998L21.707 3.70682C22.098 3.31682 22.098 2.68276 21.707 2.29276L19.707 0.292762C19.316 -0.0982383 18.6829 -0.0982383 18.2929 0.292762L11 7.58573L3.70701 0.292762C3.51151 0.0972617 3.25585 -0.000206962 2.99998 -0.000206962Z" />
|
||||
</svg> */}
|
||||
</Action>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { DndTreeItem } from './DndTreeItem';
|
||||
export { TreeItem } from './TreeItem';
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
.Wrapper {
|
||||
box-sizing: border-box;
|
||||
padding-left: var(--spacing);
|
||||
margin-bottom: -1px;
|
||||
list-style: none;
|
||||
padding: 6px 0;
|
||||
font-size: 14px;
|
||||
|
||||
&.clone {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
margin-left: 10px;
|
||||
margin-top: 5px;
|
||||
pointer-events: none;
|
||||
|
||||
.TreeItem {
|
||||
padding-right: 20px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0px 15px 15px 0 rgba(34, 33, 81, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
&.ghost {
|
||||
&.indicator {
|
||||
opacity: 1;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-bottom: -1px;
|
||||
|
||||
.TreeItem {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
height: 8px;
|
||||
border-color: #2389ff;
|
||||
background-color: #56a1f8;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
left: -8px;
|
||||
top: -4px;
|
||||
display: block;
|
||||
content: '';
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #2389ff;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
> * {
|
||||
/* Items are hidden using height and opacity to retain focus */
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.indicator) {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.TreeItem > * {
|
||||
box-shadow: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.TreeItem {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 0;
|
||||
background-color: #fff;
|
||||
color: #4c6275;
|
||||
|
||||
.TreeItemMoreActions {
|
||||
visibility: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
&:hover {
|
||||
.TreeItemMoreActions {
|
||||
visibility: visible;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Text {
|
||||
flex-grow: 1;
|
||||
padding-left: 0.5rem;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Count {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: -10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background-color: #2389ff;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.disableInteraction {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.disableSelection,
|
||||
.clone {
|
||||
.Text,
|
||||
.Count {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.Collapse {
|
||||
svg {
|
||||
transition: transform 250ms ease;
|
||||
}
|
||||
|
||||
&.collapsed svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.Action {
|
||||
display: flex;
|
||||
width: 12px;
|
||||
padding: 0 15px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
touch-action: none;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
outline: none;
|
||||
appearance: none;
|
||||
background-color: transparent;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--action-background, rgba(0, 0, 0, 0.05));
|
||||
|
||||
svg {
|
||||
fill: #6f7b88;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
flex: 0 0 auto;
|
||||
margin: auto;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
fill: #919eab;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--background, rgba(0, 0, 0, 0.05));
|
||||
|
||||
svg {
|
||||
fill: var(--fill, #788491);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0), 0 0px 0px 2px #4c9ffe;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user