Skip to content
Snippets Groups Projects
Commit 8762f823 authored by xaralis's avatar xaralis
Browse files

feat: modal basics, dropdown-menu, post/annoucement actions

parent 61206b2b
Branches
No related tags found
No related merge requests found
Showing
with 366 additions and 22 deletions
...@@ -11350,9 +11350,9 @@ ...@@ -11350,9 +11350,9 @@
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
}, },
"react-modal": { "react-modal": {
"version": "3.11.2", "version": "3.12.1",
"resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.11.2.tgz", "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.12.1.tgz",
"integrity": "sha512-o8gvvCOFaG1T7W6JUvsYjRjMVToLZgLIsi5kdhFIQCtHxDkA47LznX62j+l6YQkpXDbvQegsDyxe/+JJsFQN7w==", "integrity": "sha512-WGuXn7Fq31PbFJwtWmOk+jFtGC7E9tJVbFX0lts8ZoS5EPi9+WWylUJWLKKVm3H4GlQ7ZxY7R6tLlbSIBQ5oZA==",
"requires": { "requires": {
"exenv": "^1.2.0", "exenv": "^1.2.0",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",
......
...@@ -5,6 +5,7 @@ const Button = ({ ...@@ -5,6 +5,7 @@ const Button = ({
className, className,
iconWrapperClassName, iconWrapperClassName,
icon, icon,
color = "black",
iconChildren = null, iconChildren = null,
hoverActive = true, hoverActive = true,
fullwidth = false, fullwidth = false,
...@@ -13,6 +14,7 @@ const Button = ({ ...@@ -13,6 +14,7 @@ const Button = ({
}) => { }) => {
const btnClass = classNames( const btnClass = classNames(
"btn", "btn",
`btn--${color}`,
{ {
"btn--icon": !!icon, "btn--icon": !!icon,
"btn--hoveractive": hoverActive, "btn--hoveractive": hoverActive,
......
...@@ -3,6 +3,7 @@ import classNames from "classnames"; ...@@ -3,6 +3,7 @@ import classNames from "classnames";
import { format } from "date-fns"; import { format } from "date-fns";
import Chip from "components/Chip"; import Chip from "components/Chip";
import { DropdownMenu, DropdownMenuItem } from "components/dropdown-menu";
const Announcement = ({ const Announcement = ({
className, className,
...@@ -12,6 +13,9 @@ const Announcement = ({ ...@@ -12,6 +13,9 @@ const Announcement = ({
link, link,
relatedPostId, relatedPostId,
seen, seen,
displayActions = false,
onDelete,
onEdit,
}) => { }) => {
const wrapperClassName = classNames( const wrapperClassName = classNames(
"bg-opacity-50 border-l-2 px-4 py-2 lg:px-8 lg:py-3", "bg-opacity-50 border-l-2 px-4 py-2 lg:px-8 lg:py-3",
...@@ -49,6 +53,12 @@ const Announcement = ({ ...@@ -49,6 +53,12 @@ const Announcement = ({
const linkLabel = const linkLabel =
type === "voting" ? "Hlasovat v heliosu" : "Zobrazit související příspěvek"; type === "voting" ? "Hlasovat v heliosu" : "Zobrazit související příspěvek";
const showEdit = [
"suggested-procedure-proposal",
"voting",
"announcement",
].includes(type);
return ( return (
<div className={wrapperClassName}> <div className={wrapperClassName}>
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
...@@ -59,6 +69,22 @@ const Announcement = ({ ...@@ -59,6 +69,22 @@ const Announcement = ({
</Chip> </Chip>
{link && <a href={link}>{linkLabel + "»"}</a>} {link && <a href={link}>{linkLabel + "»"}</a>}
</div> </div>
{displayActions && (
<DropdownMenu right triggerIconClass="ico--dots-three-horizontal">
{showEdit && (
<DropdownMenuItem
onClick={onEdit}
icon="ico--edit-pencil"
title="Upravit"
/>
)}
<DropdownMenuItem
onClick={onDelete}
icon="ico--trashcan"
title="Smazat"
/>
</DropdownMenu>
)}
</div> </div>
<span className="leading-tight text-sm lg:text-base">{content}</span> <span className="leading-tight text-sm lg:text-base">{content}</span>
</div> </div>
......
...@@ -3,7 +3,21 @@ import classNames from "classnames"; ...@@ -3,7 +3,21 @@ import classNames from "classnames";
import Announcement from "./Announcement"; import Announcement from "./Announcement";
const AnnouncementList = ({ items, className }) => { const AnnouncementList = ({
items,
className,
displayActions,
onDelete,
onEdit,
}) => {
const buildHandler = (responderFn) => (post) => (evt) => {
evt.preventDefault();
responderFn(post);
};
const onAnnouncementEdit = buildHandler(onEdit);
const onAnnouncementDelete = buildHandler(onDelete);
return ( return (
<div className={classNames("space-y-px", className)}> <div className={classNames("space-y-px", className)}>
{items.map((item) => ( {items.map((item) => (
...@@ -14,6 +28,9 @@ const AnnouncementList = ({ items, className }) => { ...@@ -14,6 +28,9 @@ const AnnouncementList = ({ items, className }) => {
content={item.content} content={item.content}
link={item.link} link={item.link}
seen={item.seen} seen={item.seen}
displayActions={displayActions}
onEdit={onAnnouncementEdit(item)}
onDelete={onAnnouncementDelete(item)}
/> />
))} ))}
</div> </div>
......
import React from "react";
import classNames from "classnames";
const DropdownMenu = ({
children,
className,
right,
triggerSize = "sm",
triggerIconClass = "ico--dots-three-vertical",
}) => {
const wrapperCls = classNames(
"dropdown",
{
"dropdown--right": !!right,
},
className
);
const triggerCls = classNames(
"cursor-pointer ml-auto text-grey-200 hover:text-black",
`text-${triggerSize}`,
triggerIconClass
);
return (
<div className={wrapperCls}>
<i className={triggerCls}></i>
<ul className="dropdown__content whitespace-no-wrap">{children}</ul>
</div>
);
};
export default React.memo(DropdownMenu);
import React from "react";
import classNames from "classnames";
const DropdownMenuItem = ({
icon,
className,
onClick,
title,
titleClass,
iconSize = "2xs",
titleSize = "xs",
}) => {
const iconCls = classNames("text-2xs mr-2", `text-${iconSize}`, icon);
const titleCls = classNames(`text-${titleSize}`, titleClass);
const cls = classNames(
"dropdown__content-item bg-white hover:bg-grey-125 cursor-pointer",
className
);
return (
<li className={cls} onClick={onClick}>
<span className="block px-3 py-3">
<i className={iconCls}></i>
<span className={titleCls}>{title}</span>
</span>
</li>
);
};
export default React.memo(DropdownMenuItem);
export { default as DropdownMenu } from "./DropdownMenu";
export { default as DropdownMenuItem } from "./DropdownMenuItem";
import React from "react";
import Modal from "react-modal";
import classNames from "classnames";
const CustomModal = ({ children, containerClassName, ...props }) => (
<Modal
contentLabel={props.headline}
overlayClassName="modal__overlay"
className="modal__content"
{...props}
>
<div className={classNames("modal__container w-full", containerClassName)}>
<div className="modal__container-body">{children}</div>
</div>
</Modal>
);
export default CustomModal;
import React from "react";
import Button from "components/Button";
import Modal from "./Modal";
const ModalConfirm = ({
title,
children,
yesActionLabel = "OK",
cancelActionLabel = "Zrušit",
onCancel,
onConfirm,
...props
}) => {
return (
<Modal containerClassName="max-w-md" onRequestClose={onCancel} {...props}>
<div className="card elevation-21">
<div className="card__body">
<div className="flex items-center justify-between mb-4">
<h1 className="card-headline">{title}</h1>
<button onClick={onCancel}>
<i className="ico--close"></i>
</button>
</div>
<p className="card-body-text">{children}</p>
</div>
<div className="card-actions card-actions--right space-x-1">
<Button
hoveractive
color="blue-300"
className="text-sm"
onClick={onConfirm}
>
{yesActionLabel}
</Button>
<Button
hoveractive
color="red-600"
className="text-sm"
onClick={onConfirm}
>
{cancelActionLabel}
</Button>
</div>
</div>
</Modal>
);
};
export default ModalConfirm;
...@@ -3,6 +3,7 @@ import classNames from "classnames"; ...@@ -3,6 +3,7 @@ import classNames from "classnames";
import { format } from "date-fns"; import { format } from "date-fns";
import Chip from "components/Chip"; import Chip from "components/Chip";
import { DropdownMenu, DropdownMenuItem } from "components/dropdown-menu";
import Thumbs from "components/Thumbs"; import Thumbs from "components/Thumbs";
const Post = ({ const Post = ({
...@@ -16,9 +17,15 @@ const Post = ({ ...@@ -16,9 +17,15 @@ const Post = ({
archived, archived,
state, state,
historyLog, historyLog,
dimIfArchived = true,
displayActions = false,
onLike, onLike,
onDislike, onDislike,
dimIfArchived = true, onHide,
onBanUser,
onAnnounceProcedureProposal,
onAcceptProcedureProposal,
onRejectProcedureProposal,
}) => { }) => {
const wrapperClassName = classNames( const wrapperClassName = classNames(
"flex items-start p-4 lg:p-2 lg:py-3 lg:-mx-2", "flex items-start p-4 lg:p-2 lg:py-3 lg:-mx-2",
...@@ -102,6 +109,15 @@ const Post = ({ ...@@ -102,6 +109,15 @@ const Post = ({
logRecord.attribute === "content" && logRecord.originator === "chairman" logRecord.attribute === "content" && logRecord.originator === "chairman"
).length > 0; ).length > 0;
const showAnnounceAction =
type === "procedure-proposal" && state === "pending";
const showAcceptAction =
type === "procedure-proposal" && state === "announced";
const showRejectAction =
type === "procedure-proposal" && state === "announced";
const showBanAction = true;
const showHideAction = !archived;
return ( return (
<div className={wrapperClassName}> <div className={wrapperClassName}>
<img <img
...@@ -139,6 +155,45 @@ const Post = ({ ...@@ -139,6 +155,45 @@ const Post = ({
onDislike={onDislike} onDislike={onDislike}
canThumb={ranking.myVote === "none"} canThumb={ranking.myVote === "none"}
/> />
{displayActions && (
<DropdownMenu right>
{showAnnounceAction && (
<DropdownMenuItem
onClick={onAnnounceProcedureProposal}
icon="ico--bullhorn"
title="Vyhlásit procedurální návrh"
/>
)}
{showAcceptAction && (
<DropdownMenuItem
onClick={onAcceptProcedureProposal}
icon="ico--thumbs-up"
title="Schválit procedurální návrh"
/>
)}
{showRejectAction && (
<DropdownMenuItem
onClick={onRejectProcedureProposal}
icon="ico--thumbs-down"
title="Zamítnout procedurální návrh"
/>
)}
{showBanAction && (
<DropdownMenuItem
onClick={onBanUser}
icon="ico--lock"
title="Zablokovat uživatele"
/>
)}
{showHideAction && (
<DropdownMenuItem
onClick={onHide}
icon="ico--eye-off"
title="Skrýt příspěvek"
/>
)}
</DropdownMenu>
)}
</div> </div>
</div> </div>
</div> </div>
......
...@@ -3,18 +3,33 @@ import classNames from "classnames"; ...@@ -3,18 +3,33 @@ import classNames from "classnames";
import Post from "./Post"; import Post from "./Post";
const PostList = ({ className, items, onLike, onDislike, dimArchived }) => { const PostList = ({
const onPostLike = (post) => { className,
return (evt) => { items,
onLike(post); onLike,
}; onDislike,
}; onHide,
const onPostDislike = (post) => { onBanUser,
return (evt) => { onAnnounceProcedureProposal,
onDislike(post); onAcceptProcedureProposal,
}; onRejectProcedureProposal,
dimArchived,
}) => {
const buildHandler = (responderFn) => (post) => (evt) => {
evt.preventDefault();
responderFn(post);
}; };
const onPostLike = buildHandler(onLike);
const onPostDislike = buildHandler(onDislike);
const onPostHide = buildHandler(onHide);
const onPostBanUser = buildHandler(onBanUser);
const onPostAnnounceProcedureProposal = buildHandler(
onAnnounceProcedureProposal
);
const onPostAcceptProcedureProposal = buildHandler(onAcceptProcedureProposal);
const onPostRejectProcedureProposal = buildHandler(onRejectProcedureProposal);
return ( return (
<div className={classNames("space-y-px", className)}> <div className={classNames("space-y-px", className)}>
{items {items
...@@ -31,9 +46,15 @@ const PostList = ({ className, items, onLike, onDislike, dimArchived }) => { ...@@ -31,9 +46,15 @@ const PostList = ({ className, items, onLike, onDislike, dimArchived }) => {
historyLog={item.historyLog} historyLog={item.historyLog}
seen={item.seen} seen={item.seen}
archived={item.archived} archived={item.archived}
displayActions={true}
dimIfArchived={dimArchived}
onLike={onPostLike(item)} onLike={onPostLike(item)}
onDislike={onPostDislike(item)} onDislike={onPostDislike(item)}
dimIfArchived={dimArchived} onHide={onPostHide(item)}
onBanUser={onPostBanUser(item)}
onAnnounceProcedureProposal={onPostAnnounceProcedureProposal(item)}
onAcceptProcedureProposal={onPostAcceptProcedureProposal(item)}
onRejectProcedureProposal={onPostRejectProcedureProposal(item)}
/> />
))} ))}
</div> </div>
......
...@@ -34,7 +34,7 @@ const AddAnnouncementForm = ({ className }) => { ...@@ -34,7 +34,7 @@ const AddAnnouncementForm = ({ className }) => {
<Button <Button
onClick={onAdd} onClick={onAdd}
className="btn--black text-sm mt-2" className="text-sm mt-2"
hoverActive hoverActive
disabled={!text} disabled={!text}
> >
......
...@@ -53,7 +53,6 @@ const AddPostForm = ({ className }) => { ...@@ -53,7 +53,6 @@ const AddPostForm = ({ className }) => {
<div className="space-x-4"> <div className="space-x-4">
<Button <Button
className="btn--black"
onClick={onAddPost} onClick={onAddPost}
disabled={!text} disabled={!text}
hoverActive hoverActive
......
...@@ -6,7 +6,21 @@ import { AnnouncementStore } from "stores"; ...@@ -6,7 +6,21 @@ import { AnnouncementStore } from "stores";
const AnnoucementsContainer = () => { const AnnoucementsContainer = () => {
const items = AnnouncementStore.useState((state) => state.items); const items = AnnouncementStore.useState((state) => state.items);
return <AnnouncementList items={items} />; const onEdit = (announcement) => {
console.log("edit", announcement);
};
const onDelete = (announcement) => {
console.log("delete", announcement);
};
return (
<AnnouncementList
items={items}
displayActions={true}
onDelete={onDelete}
onEdit={onEdit}
/>
);
}; };
export default AnnoucementsContainer; export default AnnoucementsContainer;
...@@ -19,6 +19,22 @@ const PostsContainer = ({ className }) => { ...@@ -19,6 +19,22 @@ const PostsContainer = ({ className }) => {
const sliceStart = (window.page - 1) * window.perPage; const sliceStart = (window.page - 1) * window.perPage;
const sliceEnd = window.page * window.perPage; const sliceEnd = window.page * window.perPage;
const onHide = (post) => {
console.log("hide", post);
};
const onBanUser = (post) => {
console.log("banUser", post);
};
const onAnnounceProcedureProposal = (post) => {
console.log("announce", post);
};
const onAcceptProcedureProposal = (post) => {
console.log("accept", post);
};
const onRejectProcedureProposal = (post) => {
console.log("reject", post);
};
return ( return (
<PostList <PostList
items={window.items items={window.items
...@@ -28,6 +44,12 @@ const PostsContainer = ({ className }) => { ...@@ -28,6 +44,12 @@ const PostsContainer = ({ className }) => {
onDislike={dislike.run} onDislike={dislike.run}
className={className} className={className}
dimArchived={!showingArchivedOnly} dimArchived={!showingArchivedOnly}
displayActions={true}
onHide={onHide}
onBanUser={onBanUser}
onAnnounceProcedureProposal={onAnnounceProcedureProposal}
onAcceptProcedureProposal={onAcceptProcedureProposal}
onRejectProcedureProposal={onRejectProcedureProposal}
/> />
); );
}; };
......
import React from "react"; import React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import ReactModal from "react-modal";
import App from "./App"; import App from "./App";
import * as serviceWorker from "./serviceWorker"; import * as serviceWorker from "./serviceWorker";
const root = document.getElementById("root");
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>,
document.getElementById("root") root
); );
ReactModal.setAppElement(root);
// If you want your app to work offline and load faster, you can change // If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls. // unregister() to register() below. Note this comes with some pitfalls.
......
import React from "react"; import React, { useState } from "react";
import { DropdownMenu, DropdownMenuItem } from "components/dropdown-menu";
import ModalConfirm from "components/modals/ModalConfirm";
import AddAnnouncementForm from "containers/AddAnnouncementForm"; import AddAnnouncementForm from "containers/AddAnnouncementForm";
import AddPostForm from "containers/AddPostForm"; import AddPostForm from "containers/AddPostForm";
import AnnouncementsContainer from "containers/AnnoucementsContainer"; import AnnouncementsContainer from "containers/AnnoucementsContainer";
...@@ -7,6 +9,21 @@ import PostFilters from "containers/PostFilters"; ...@@ -7,6 +9,21 @@ import PostFilters from "containers/PostFilters";
import PostsContainer from "containers/PostsContainer"; import PostsContainer from "containers/PostsContainer";
const Home = () => { const Home = () => {
const [showEndDiscussionConfirm, setShowEndDiscussionConfirm] = useState(
false
);
const onRenameProgramPoint = () => {
console.log("renameProgramPoint");
};
const onEndDiscussion = () => {
console.log("endDiscussion");
setShowEndDiscussionConfirm(true);
};
const onEndProgramPoint = () => {
console.log("endProgramPoint");
};
return ( return (
<> <>
<article className="container container--wide pt-8 lg:py-24 cf2021"> <article className="container container--wide pt-8 lg:py-24 cf2021">
...@@ -16,6 +33,30 @@ const Home = () => { ...@@ -16,6 +33,30 @@ const Home = () => {
Bod č. 1: Programové priority Pirátské strany pro sněmovní volby Bod č. 1: Programové priority Pirátské strany pro sněmovní volby
2021 2021
</h1> </h1>
<DropdownMenu right triggerSize="lg">
<DropdownMenuItem
onClick={onRenameProgramPoint}
icon="ico--edit-pencil"
title="Přejmenovat bod programu"
titleSize="base"
iconSize="base"
/>
<DropdownMenuItem
onClick={onEndDiscussion}
icon="ico--bubbles"
title="Ukončit rozpravu"
titleSize="base"
iconSize="base"
/>
<DropdownMenuItem
onClick={onEndProgramPoint}
icon="ico--switch"
title="Ukončit bod programu"
titleSize="base"
iconSize="base"
/>
)}
</DropdownMenu>
</div> </div>
<iframe <iframe
...@@ -52,6 +93,15 @@ const Home = () => { ...@@ -52,6 +93,15 @@ const Home = () => {
<AddPostForm className="my-8 space-y-4" /> <AddPostForm className="my-8 space-y-4" />
</section> </section>
</article> </article>
<ModalConfirm
isOpen={showEndDiscussionConfirm}
onConfirm={() => setShowEndDiscussionConfirm(false)}
onCancel={() => setShowEndDiscussionConfirm(false)}
title="Ukončit rozpravu?"
yesActionLabel="Ukončit"
>
Opravdu chcete ukončit rozpravu?
</ModalConfirm>
</> </>
); );
}; };
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment