Skip to content

Commit

Permalink
Feature/mobile improvements (#4)
Browse files Browse the repository at this point in the history
* better variable naming

* add resize handler and menu button to toggle menu

* remove comment

* refactor to use common component for rendering items to unify styling changes

* add some basic mobile nav handling

* hide menu when option is selected

* init size variables that we set on screen resize

* refactor repeat code to function
  • Loading branch information
Mike Zrimsek authored Apr 12, 2024
1 parent f0816f8 commit c8e9500
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 53 deletions.
25 changes: 25 additions & 0 deletions app/components/HamburgerButton.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from "react";

// add a prop type interface with a property called onClick that is a function that returns void
interface HamburgerButtonProps {
onClick: () => void;
}

const HamburgerButton: React.FC<HamburgerButtonProps> = ({ onClick }) => {
return (
<button
className="navbar-burger flex items-center p-3 text-black dark:text-white"
onClick={onClick}
>
<svg
className="block h-4 w-4 fill-current"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"></path>
</svg>
</button>
);
};

export default HamburgerButton;
2 changes: 1 addition & 1 deletion app/components/MergedTopicsMessageList.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const MergedTopicsMessageList: React.FC<MergedTopicsMessageListProps> = ({
doTopicColoring,
}) => {
const mergedMessages = topicMessages
.map((x) => x.messages)
.map((messages) => messages.messages)
.flat()
.sort((a, b) => a.time - b.time);

Expand Down
60 changes: 39 additions & 21 deletions app/containers/MessagesByTag.component.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import React from "react";
import Menu from "~/components/Menu.component";
import MergedTopicsMessageList from "~/components/MergedTopicsMessageList.component";
import { NtfyMessage, Topic, TopicMessages, UNTAGGED } from "~/models";
import { getMessagesForTopic, getTopicConfig } from "~/utils";
import MessagesDisplay from "./MessagesDisplay.component";

type MessagesByTagProps = {
messageMap: Record<string, NtfyMessage>;
topics: Array<Topic>;
tags: Array<string>;
selectedTag: string;
setSelectedTag: (index: string) => void;
showMenu: boolean;
setShowMenu: (showMenu: boolean) => void;
screenSize: number;
};

const MessagesByTag: React.FC<MessagesByTagProps> = ({
Expand All @@ -18,14 +21,19 @@ const MessagesByTag: React.FC<MessagesByTagProps> = ({
tags,
selectedTag,
setSelectedTag,
showMenu,
setShowMenu,
screenSize,
}) => {
const topicNames = topics.map((topic) => topic.name);
const sortedTopics = topicNames.sort((a, b) => a.localeCompare(b));
const topicConfigs = sortedTopics.map((topic) =>
getTopicConfig(topic, topics)
);

const topicConfigsWithNoTags = topicConfigs.filter((x) => !x?.tags?.length);
const topicConfigsWithNoTags = topicConfigs.filter(
(topic) => !topic?.tags?.length
);
const untaggedTopicMessagesList = topicConfigsWithNoTags.map(
(topicConfig) => {
const messages = getMessagesForTopic(messageMap, topicConfig?.name);
Expand All @@ -36,7 +44,9 @@ const MessagesByTag: React.FC<MessagesByTagProps> = ({
}
);

const topicConfigsWithTags = topicConfigs.filter((x) => x?.tags?.length);
const topicConfigsWithTags = topicConfigs.filter(
(topic) => topic?.tags?.length
);
const topicMessagesList = topicConfigsWithTags.map((topicConfig) => {
const messages = getMessagesForTopic(messageMap, topicConfig?.name);
return {
Expand All @@ -49,29 +59,37 @@ const MessagesByTag: React.FC<MessagesByTagProps> = ({
if (tag === UNTAGGED) {
return untaggedTopicMessagesList;
}
return topicMessagesList.filter((x) => x.topicConfig?.tags?.includes(tag));
return topicMessagesList.filter((messages) =>
messages.topicConfig?.tags?.includes(tag)
);
};

const getMessageCountForTag = (tag: string) => {
return getMessagesForTag(tag).reduce(
(acc, messages) => acc + messages.messages.length,
0
);
};

const tagNames = [UNTAGGED, ...tags];

return (
<div className="grid">
<div className="w-1/5 border-r border-gray-200 fixed overflow-y-auto top-20 bottom-0">
<Menu
options={tagNames}
selectedOption={selectedTag}
setSelectedOption={setSelectedTag}
hideAllOption
/>
</div>
<div className="w-4/5 px-4 py-4 overflow-auto justify-self-end">
<MergedTopicsMessageList
tag={selectedTag}
topicMessages={getMessagesForTag(selectedTag)}
doTopicColoring
></MergedTopicsMessageList>
</div>
</div>
<MessagesDisplay
menuOptions={tagNames}
showMenu={showMenu}
setShowMenu={setShowMenu}
hideAllOption
selectedOption={selectedTag}
setSelectedOption={setSelectedTag}
getMessageCountForSelectedOption={getMessageCountForTag}
screenSize={screenSize}
>
<MergedTopicsMessageList
tag={selectedTag}
topicMessages={getMessagesForTag(selectedTag)}
doTopicColoring
></MergedTopicsMessageList>
</MessagesDisplay>
);
};

Expand Down
57 changes: 27 additions & 30 deletions app/containers/MessagesByTopic.component.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import Menu from "~/components/Menu.component";
import TopicMessageList from "~/components/TopicMessageList.component";
import { ALL_OPTIONS, MessageMetadata, NtfyMessage, Topic } from "~/models";
import MessagesDisplay from "./MessagesDisplay.component";

type MessagesByTopicProps = {
messageMap: Record<string, NtfyMessage>;
Expand All @@ -10,6 +10,9 @@ type MessagesByTopicProps = {
selectedTopic: string;
setSelectedTopic: (topic: string) => void;
acknowledgeTopic?: (topic: string) => void;
showMenu: boolean;
setShowMenu: (showMenu: boolean) => void;
screenSize: number;
};

const MessagesByTopic: React.FC<MessagesByTopicProps> = ({
Expand All @@ -19,6 +22,9 @@ const MessagesByTopic: React.FC<MessagesByTopicProps> = ({
selectedTopic,
setSelectedTopic,
acknowledgeTopic,
showMenu,
setShowMenu,
screenSize,
}) => {
const getMessagesForSelectedTopic = () => {
let messages: Array<NtfyMessage> = Object.values(messageMap);
Expand Down Expand Up @@ -46,42 +52,33 @@ const MessagesByTopic: React.FC<MessagesByTopicProps> = ({

const getMessageCountForTopic = (topic: string) => {
const messageMetadata = Object.values(messageMetadataMap);
const messagesForTopic = messageMetadata.filter((x) => x.topic === topic);
const messagesForTopic = messageMetadata.filter(
(metadata) => metadata.topic === topic
);
const unacknowledgedMessages = messagesForTopic.filter(
(x) => !x.acknowledged
);
return unacknowledgedMessages.length;
};

return (
<div className="grid">
<div className="w-1/5 border-r border-gray-200 fixed overflow-y-auto top-20 bottom-0">
<Menu
options={getTopicNames()}
selectedOption={selectedTopic}
setSelectedOption={setSelectedTopic}
getCountForOption={getMessageCountForTopic}
/>
</div>
<div className="w-4/5 px-4 py-4 overflow-auto justify-self-end">
<div className="flex items-center justify-between">
<h1 className="text-xl font-bold mb-0 mr-4">{getTitle()}</h1>
{shouldRenderTopicAcknowledgementButton && (
<button
className="px-4 py-2 rounded-md font-medium bg-blue-500 text-white hover:bg-gray-200 hover:text-gray-800"
onClick={() => acknowledgeTopic(selectedTopic)}
>
Acknowledge Topic
</button>
)}
</div>
<TopicMessageList
messages={messages}
doTopicColoring={doTopicColoring}
/>
</div>
</div>
<MessagesDisplay
menuOptions={getTopicNames()}
showMenu={showMenu}
setShowMenu={setShowMenu}
selectedOption={selectedTopic}
setSelectedOption={setSelectedTopic}
getMessageCountForSelectedOption={getMessageCountForTopic}
title={getTitle()}
shouldRenderTopicAcknowledgementButton={
shouldRenderTopicAcknowledgementButton
}
acknowledgeSelectedOption={acknowledgeTopic}
screenSize={screenSize}
>
<TopicMessageList messages={messages} doTopicColoring={doTopicColoring} />
</MessagesDisplay>
);
};
// px-4 py-2 rounded-md font-medium bg-blue-500 text-white hover:bg-gray-200 hover:text-gray-800

export default MessagesByTopic;
95 changes: 95 additions & 0 deletions app/containers/MessagesDisplay.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from "react";

import Menu from "~/components/Menu.component";
import { SCREEN_SIZES } from "~/models";

interface MessagesDisplayProps {
children?: React.ReactNode;
menuOptions: Array<string>;
showMenu: boolean;
setShowMenu: (showMenu: boolean) => void;
hideAllOption?: boolean;
selectedOption: string;
setSelectedOption: (option: string) => void;
getMessageCountForSelectedOption: (option: string) => number;
title?: string;
shouldRenderTopicAcknowledgementButton?: boolean;
acknowledgeSelectedOption?: (option: string) => void;
screenSize: number;
}

const MessagesDisplay: React.FC<MessagesDisplayProps> = ({
children,
menuOptions,
showMenu,
setShowMenu,
hideAllOption,
selectedOption,
setSelectedOption,
getMessageCountForSelectedOption,
shouldRenderTopicAcknowledgementButton,
title,
acknowledgeSelectedOption,
screenSize,
}) => {
const shouldRenderAcknowledgementButton =
acknowledgeSelectedOption && shouldRenderTopicAcknowledgementButton;

const listClassNameBase = "px-4 py-4 overflow-auto justify-self-end";
const listClassName = showMenu
? `${listClassNameBase} w-4/5`
: `${listClassNameBase} w-full`;

const menuClassNameBase =
"border-r border-gray-200 fixed overflow-y-auto top-20 bottom-0";
const getMenuClassName = () => {
if (screenSize >= SCREEN_SIZES.md) {
return `${menuClassNameBase} w-1/5`;
}
return showMenu ? `${menuClassNameBase} w-full` : "hidden";
};

const shouldRenderList = screenSize >= SCREEN_SIZES.md || !showMenu;

const handleOptionSelected = (option: string) => {
// if the screen is small, hide the menu when an option is selected
if (screenSize < SCREEN_SIZES.md) {
setShowMenu(false);
}
setSelectedOption(option);
};

return (
<div className="grid">
{showMenu && (
<div className={getMenuClassName()}>
<Menu
options={menuOptions}
selectedOption={selectedOption}
setSelectedOption={handleOptionSelected}
getCountForOption={getMessageCountForSelectedOption}
hideAllOption={hideAllOption}
/>
</div>
)}
{shouldRenderList && (
<div className={listClassName}>
<div className="flex items-center justify-between">
{title && <h1 className="text-xl font-bold mb-0 mr-4">{title}</h1>}
{shouldRenderAcknowledgementButton && (
<button
className="px-4 py-2 rounded-md font-medium bg-blue-500 text-white hover:bg-gray-200 hover:text-gray-800"
onClick={() => acknowledgeSelectedOption(selectedOption)}
>
Acknowledge Topic
</button>
)}
</div>
{children}
</div>
)}
</div>
);
};

export default MessagesDisplay;
7 changes: 7 additions & 0 deletions app/models/constants.models.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
export const ALL_OPTIONS = "ALL_OPTIONS";
export const UNTAGGED = "untagged";

export const SCREEN_SIZES = {
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
};
Loading

0 comments on commit c8e9500

Please sign in to comment.