mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
added notes menu
This commit is contained in:
parent
fce11163aa
commit
57f37723fa
14 changed files with 203 additions and 105 deletions
|
@ -1,6 +1,6 @@
|
|||
#NEXT_PUBLIC_API_HOST=https://pig.staging.vault48.org/
|
||||
#NEXT_PUBLIC_REMOTE_CURRENT=https://pig.staging.vault48.org/static/
|
||||
NEXT_PUBLIC_API_HOST=http://localhost:8888/
|
||||
NEXT_PUBLIC_REMOTE_CURRENT=http://localhost:8888/static/
|
||||
#NEXT_PUBLIC_API_HOST=https://pig.vault48.org/
|
||||
#NEXT_PUBLIC_REMOTE_CURRENT=https://pig.vault48.org/static/
|
||||
#NEXT_PUBLIC_API_HOST=http://localhost:8888/
|
||||
#NEXT_PUBLIC_REMOTE_CURRENT=http://localhost:8888/static/
|
||||
NEXT_PUBLIC_API_HOST=https://pig.vault48.org/
|
||||
NEXT_PUBLIC_REMOTE_CURRENT=https://pig.vault48.org/static/
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import React, { FC, useCallback, useState } from 'react';
|
||||
import React, { FC, useMemo } from 'react';
|
||||
|
||||
import { MenuDots } from '~/components/common/MenuDots';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
import { CornerMenu } from '~/components/common/CornerMenu';
|
||||
|
||||
interface IProps {
|
||||
onEdit: () => void;
|
||||
|
@ -10,27 +8,18 @@ interface IProps {
|
|||
}
|
||||
|
||||
const CommentMenu: FC<IProps> = ({ onEdit, onDelete }) => {
|
||||
const [is_menu_opened, setIsMenuOpened] = useState(false);
|
||||
|
||||
const onFocus = useCallback(() => setIsMenuOpened(true), [setIsMenuOpened]);
|
||||
const onBlur = useCallback(() => setIsMenuOpened(false), [setIsMenuOpened]);
|
||||
|
||||
return (
|
||||
<div className={styles.wrap} onFocus={onFocus} onBlur={onBlur} tabIndex={-1}>
|
||||
<MenuDots onClick={onFocus} />
|
||||
|
||||
{is_menu_opened && (
|
||||
<div className={styles.menu}>
|
||||
<div className={styles.item} onMouseDown={onEdit}>
|
||||
Редактировать
|
||||
</div>
|
||||
<div className={styles.item} onMouseDown={onDelete}>
|
||||
Удалить
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
const actions = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: 'Редактировать',
|
||||
action: onEdit,
|
||||
},
|
||||
{ title: 'Удалить', action: onDelete },
|
||||
],
|
||||
[onEdit, onDelete]
|
||||
);
|
||||
|
||||
return <CornerMenu actions={actions} />;
|
||||
};
|
||||
|
||||
export { CommentMenu };
|
||||
|
|
37
src/components/common/CornerMenu/index.tsx
Normal file
37
src/components/common/CornerMenu/index.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
import React, { useCallback, useState, VFC } from 'react';
|
||||
|
||||
import styles from '~/components/comment/CommentMenu/styles.module.scss';
|
||||
import { MenuDots } from '~/components/common/MenuDots';
|
||||
|
||||
interface MenuAction {
|
||||
title: string;
|
||||
action: () => void;
|
||||
}
|
||||
interface CornerMenuProps {
|
||||
actions: MenuAction[];
|
||||
}
|
||||
|
||||
const CornerMenu: VFC<CornerMenuProps> = ({ actions }) => {
|
||||
const [is_menu_opened, setIsMenuOpened] = useState(false);
|
||||
|
||||
const onFocus = useCallback(() => setIsMenuOpened(true), [setIsMenuOpened]);
|
||||
const onBlur = useCallback(() => setIsMenuOpened(false), [setIsMenuOpened]);
|
||||
|
||||
return (
|
||||
<div className={styles.wrap} onFocus={onFocus} onBlur={onBlur} tabIndex={-1}>
|
||||
<MenuDots onClick={onFocus} />
|
||||
|
||||
{is_menu_opened && (
|
||||
<div className={styles.menu}>
|
||||
{actions.map(({ title, action }) => (
|
||||
<div className={styles.item} onMouseDown={action} key={title}>
|
||||
{title}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { CornerMenu };
|
62
src/components/common/CornerMenu/styles.module.scss
Normal file
62
src/components/common/CornerMenu/styles.module.scss
Normal file
|
@ -0,0 +1,62 @@
|
|||
@import "src/styles/variables";
|
||||
|
||||
.wrap {
|
||||
position: absolute;
|
||||
right: -3px;
|
||||
top: -3px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
z-index: 10;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
.dot {
|
||||
background: $secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes appear {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 6;
|
||||
white-space: nowrap;
|
||||
|
||||
animation: appear 0.25s forwards;
|
||||
}
|
||||
|
||||
.item {
|
||||
user-select: none;
|
||||
font: $font_12_semibold;
|
||||
text-transform: uppercase;
|
||||
padding: 8px $gap;
|
||||
background: $content_bg;
|
||||
cursor: pointer;
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: $radius;
|
||||
border-top-right-radius: $radius;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom-left-radius: $radius;
|
||||
border-bottom-right-radius: $radius;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $primary;
|
||||
}
|
||||
}
|
22
src/components/containers/Columns/index.tsx
Normal file
22
src/components/containers/Columns/index.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import Masonry from 'react-masonry-css';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
||||
const defaultColumns = {
|
||||
default: 2,
|
||||
1280: 1,
|
||||
};
|
||||
|
||||
interface ColumnsProps {
|
||||
cols?: Record<number, number>;
|
||||
}
|
||||
|
||||
const Columns: FC<ColumnsProps> = ({ children, cols = defaultColumns }) => (
|
||||
<Masonry className={styles.wrap} breakpointCols={cols} columnClassName={styles.column}>
|
||||
{children}
|
||||
</Masonry>
|
||||
);
|
||||
|
||||
export { Columns };
|
27
src/components/containers/Columns/styles.module.scss
Normal file
27
src/components/containers/Columns/styles.module.scss
Normal file
|
@ -0,0 +1,27 @@
|
|||
@import "src/styles/variables";
|
||||
@import "src/styles/mixins";
|
||||
|
||||
div.wrap {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
padding: $gap $gap * 0.5;
|
||||
|
||||
@include tablet {
|
||||
padding: 0 $gap * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.column {
|
||||
background-clip: padding-box;
|
||||
box-sizing: border-box;
|
||||
padding: 0 $gap * 0.5;
|
||||
|
||||
@include tablet {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
& > div {
|
||||
margin-bottom: $gap;
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ import React, { VFC } from 'react';
|
|||
import { Card } from '~/components/containers/Card';
|
||||
import { Markdown } from '~/components/containers/Markdown';
|
||||
import { Padder } from '~/components/containers/Padder';
|
||||
import { NoteMenu } from '~/components/notes/NoteMenu';
|
||||
import { formatText, getPrettyDate } from '~/utils/dom';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
|
@ -15,8 +16,10 @@ interface NoteCardProps {
|
|||
const NoteCard: VFC<NoteCardProps> = ({ content, createdAt }) => (
|
||||
<Card className={styles.note}>
|
||||
<Padder>
|
||||
<NoteMenu onEdit={console.log} onDelete={console.log} />
|
||||
<Markdown className={styles.wrap} dangerouslySetInnerHTML={{ __html: formatText(content) }} />
|
||||
</Padder>
|
||||
|
||||
<Padder className={styles.footer}>{getPrettyDate(createdAt)}</Padder>
|
||||
</Card>
|
||||
);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
min-width: 0;
|
||||
word-break: break-word;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
|
||||
& > * {
|
||||
@include row_shadow;
|
||||
|
|
25
src/components/notes/NoteMenu/index.tsx
Normal file
25
src/components/notes/NoteMenu/index.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React, { useMemo, VFC } from 'react';
|
||||
|
||||
import { CornerMenu } from '~/components/common/CornerMenu';
|
||||
|
||||
interface NoteMenuProps {
|
||||
onEdit: () => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
const NoteMenu: VFC<NoteMenuProps> = ({ onEdit, onDelete }) => {
|
||||
const actions = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: 'Редактировать',
|
||||
action: onEdit,
|
||||
},
|
||||
{ title: 'Удалить', action: onDelete },
|
||||
],
|
||||
[onEdit, onDelete]
|
||||
);
|
||||
|
||||
return <CornerMenu actions={actions} />;
|
||||
};
|
||||
|
||||
export { NoteMenu };
|
|
@ -2,6 +2,7 @@ import React, { FC } from 'react';
|
|||
|
||||
import Masonry from 'react-masonry-css';
|
||||
|
||||
import { Columns } from '~/components/containers/Columns';
|
||||
import { InfiniteScroll } from '~/components/containers/InfiniteScroll';
|
||||
import { LabNoResults } from '~/components/lab/LabNoResults';
|
||||
import { LabNode } from '~/components/lab/LabNode';
|
||||
|
@ -35,11 +36,7 @@ const LabGrid: FC<IProps> = () => {
|
|||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Masonry
|
||||
className={styles.wrap}
|
||||
breakpointCols={breakpointCols}
|
||||
columnClassName={styles.column}
|
||||
>
|
||||
<Columns>
|
||||
<LoadingNode />
|
||||
<LoadingNode />
|
||||
<LoadingNode />
|
||||
|
@ -49,7 +46,7 @@ const LabGrid: FC<IProps> = () => {
|
|||
<LoadingNode />
|
||||
<LoadingNode />
|
||||
<LoadingNode />
|
||||
</Masonry>
|
||||
</Columns>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -59,11 +56,7 @@ const LabGrid: FC<IProps> = () => {
|
|||
|
||||
return (
|
||||
<InfiniteScroll hasMore={hasMore} loadMore={loadMore}>
|
||||
<Masonry
|
||||
className={styles.wrap}
|
||||
breakpointCols={breakpointCols}
|
||||
columnClassName={styles.column}
|
||||
>
|
||||
<Columns>
|
||||
{nodes.map(node => (
|
||||
<LabNode
|
||||
node={node.node}
|
||||
|
@ -72,7 +65,7 @@ const LabGrid: FC<IProps> = () => {
|
|||
commentCount={node.comment_count}
|
||||
/>
|
||||
))}
|
||||
</Masonry>
|
||||
</Columns>
|
||||
</InfiniteScroll>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
@import "src/styles/variables.scss";
|
||||
|
||||
div.wrap {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
|
||||
@include tablet {
|
||||
padding: 0 $gap * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.column {
|
||||
background-clip: padding-box;
|
||||
box-sizing: border-box;
|
||||
padding: 0 $gap * 0.5;
|
||||
margin-top: -$gap * 0.5;
|
||||
|
||||
@include tablet {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
& > div {
|
||||
margin-bottom: $gap;
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ import React, { useState, VFC } from 'react';
|
|||
import Masonry from 'react-masonry-css';
|
||||
|
||||
import { Card } from '~/components/containers/Card';
|
||||
import { Columns } from '~/components/containers/Columns';
|
||||
import { Filler } from '~/components/containers/Filler';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Markdown } from '~/components/containers/Markdown';
|
||||
|
@ -20,11 +21,6 @@ import styles from './styles.module.scss';
|
|||
|
||||
interface SettingsNotesProps {}
|
||||
|
||||
const breakpointCols = {
|
||||
default: 2,
|
||||
1280: 1,
|
||||
};
|
||||
|
||||
const SettingsNotes: VFC<SettingsNotesProps> = () => {
|
||||
const [text, setText] = useState('');
|
||||
const { notes } = useGetNotes('');
|
||||
|
@ -44,11 +40,7 @@ const SettingsNotes: VFC<SettingsNotesProps> = () => {
|
|||
</Group>
|
||||
</Padder>
|
||||
|
||||
<Masonry
|
||||
className={styles.wrap}
|
||||
breakpointCols={breakpointCols}
|
||||
columnClassName={styles.column}
|
||||
>
|
||||
<Columns>
|
||||
<Card>
|
||||
<Group>
|
||||
<Textarea handler={setText} value={text} />
|
||||
|
@ -63,7 +55,7 @@ const SettingsNotes: VFC<SettingsNotesProps> = () => {
|
|||
{notes.map(note => (
|
||||
<NoteCard key={note.id} content={note.content} createdAt={note.created_at} />
|
||||
))}
|
||||
</Masonry>
|
||||
</Columns>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
@import "src/styles/variables";
|
||||
@import "src/styles/mixins";
|
||||
|
||||
div.wrap {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
padding: $gap $gap * 0.5;
|
||||
|
||||
@include tablet {
|
||||
padding: 0 $gap * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.column {
|
||||
background-clip: padding-box;
|
||||
box-sizing: border-box;
|
||||
padding: 0 $gap * 0.5;
|
||||
|
||||
@include tablet {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
& > div {
|
||||
margin-bottom: $gap;
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue