From f12bf7a8c5085052ed755b095b3505ce7f8ae288 Mon Sep 17 00:00:00 2001 From: Kristina N <87397396+kstinanguyen@users.noreply.github.com> Date: Wed, 11 Dec 2024 13:53:39 -0800 Subject: [PATCH 1/4] wave 1 and wave 2 implemented --- src/App.jsx | 3 +++ src/components/ChatEntry.jsx | 28 ++++++++++++++++++---------- src/components/ChatLog.jsx | 31 +++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 src/components/ChatLog.jsx diff --git a/src/App.jsx b/src/App.jsx index 14a7f684d..c09b9f3f0 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,4 +1,6 @@ import './App.css'; +import chatData from './data/messages.json'; +import ChatLog from './components/ChatLog'; const App = () => { return ( @@ -9,6 +11,7 @@ const App = () => {
{/* Wave 01: Render one ChatEntry component Wave 02: Render ChatLog component */} +
); diff --git a/src/components/ChatEntry.jsx b/src/components/ChatEntry.jsx index 15c56f96b..d3759eb3c 100644 --- a/src/components/ChatEntry.jsx +++ b/src/components/ChatEntry.jsx @@ -1,20 +1,28 @@ import './ChatEntry.css'; +import PropTypes from 'prop-types'; +import TimeStamp from './TimeStamp'; -const ChatEntry = () => { +const ChatEntry = ({ sender, body, timeStamp }) => { return ( -
-

Replace with name of sender

-
-

Replace with body of ChatEntry

-

Replace with TimeStamp component

- -
-
+ <> +
+

{sender}

+
+

{body}

+

+ +

+ +
+
+ ); }; ChatEntry.propTypes = { - // Fill with correct proptypes + sender: PropTypes.string.isRequired, + body: PropTypes.string.isRequired, + timeStamp: PropTypes.string.isRequired, }; export default ChatEntry; diff --git a/src/components/ChatLog.jsx b/src/components/ChatLog.jsx new file mode 100644 index 000000000..f47703888 --- /dev/null +++ b/src/components/ChatLog.jsx @@ -0,0 +1,31 @@ +import PropTypes from 'prop-types'; +import ChatEntry from './ChatEntry'; + +const ChatLog = ({ entries }) => { + const chatEntryComponents = entries.map((entry) => { + return ( + + ); + }); + + return ( + + ); +}; + +ChatLog.propTypes = { + entries: PropTypes.arrayOf(PropTypes.shape({ + sender: PropTypes.string.isRequired, + body: PropTypes.string.isRequired, + timeStamp: PropTypes.string.isRequired, + })).isRequired +}; + +export default ChatLog; From 19a93410b63786fe5f2de23ab2b8bf3e636f895c Mon Sep 17 00:00:00 2001 From: Kristina N <87397396+kstinanguyen@users.noreply.github.com> Date: Thu, 12 Dec 2024 16:28:53 -0800 Subject: [PATCH 2/4] wave 3 implemented, all tests passing --- src/App.css | 6 ++---- src/App.jsx | 24 +++++++++++++++++++++--- src/components/ChatEntry.jsx | 13 +++++++++++-- src/components/ChatLog.jsx | 10 ++++++++-- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/App.css b/src/App.css index d97beb4e6..6ff25f541 100644 --- a/src/App.css +++ b/src/App.css @@ -5,7 +5,6 @@ #App header { background-color: #222; color: #fff; - padding-bottom: 0.5rem; position: fixed; width: 100%; z-index: 100; @@ -36,13 +35,12 @@ border-radius: 10px; color: black; font-size:0.8em; - padding-left: 1em; - padding-right: 1em; + padding: 1em; } #App #heartWidget { font-size: 1.5em; - margin: 1em + margin: 1em; } #App span { diff --git a/src/App.jsx b/src/App.jsx index c09b9f3f0..c7caae800 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,17 +1,35 @@ import './App.css'; -import chatData from './data/messages.json'; +import CHATS from './data/messages.json'; import ChatLog from './components/ChatLog'; +import { useState } from 'react'; const App = () => { + const [chatData, setChatData] = useState(CHATS); + + const handleLikedMessages = (id) => { + setChatData(chatData => chatData.map(chat => { + if (chat.id === id) { + return { ...chat, liked: !chat.liked }; + } else { + return chat; + } + })); + }; + + const likedCount = chatData.filter((chat) => chat.liked).length; + return (
-

Application title

+

Chat thread between Name1 and Name2

+
+ {likedCount} ❤️s +
{/* Wave 01: Render one ChatEntry component Wave 02: Render ChatLog component */} - +
); diff --git a/src/components/ChatEntry.jsx b/src/components/ChatEntry.jsx index d3759eb3c..ac3926366 100644 --- a/src/components/ChatEntry.jsx +++ b/src/components/ChatEntry.jsx @@ -2,7 +2,13 @@ import './ChatEntry.css'; import PropTypes from 'prop-types'; import TimeStamp from './TimeStamp'; -const ChatEntry = ({ sender, body, timeStamp }) => { +const ChatEntry = ({ id, sender, body, timeStamp, liked, onLiked }) => { + const onHeartClick = () => { + onLiked(id); + }; + + const filledHeart = liked ? '❤️' : '🤍'; + return ( <>
@@ -12,7 +18,7 @@ const ChatEntry = ({ sender, body, timeStamp }) => {

- +
@@ -20,9 +26,12 @@ const ChatEntry = ({ sender, body, timeStamp }) => { }; ChatEntry.propTypes = { + id: PropTypes.number.isRequired, sender: PropTypes.string.isRequired, body: PropTypes.string.isRequired, timeStamp: PropTypes.string.isRequired, + liked: PropTypes.bool.isRequired, + onLiked: PropTypes.func.isRequired, }; export default ChatEntry; diff --git a/src/components/ChatLog.jsx b/src/components/ChatLog.jsx index f47703888..ce6c49c88 100644 --- a/src/components/ChatLog.jsx +++ b/src/components/ChatLog.jsx @@ -1,13 +1,16 @@ import PropTypes from 'prop-types'; import ChatEntry from './ChatEntry'; -const ChatLog = ({ entries }) => { +const ChatLog = ({ entries, onLiked }) => { const chatEntryComponents = entries.map((entry) => { return ( ); @@ -22,10 +25,13 @@ const ChatLog = ({ entries }) => { ChatLog.propTypes = { entries: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.number.isRequired, sender: PropTypes.string.isRequired, body: PropTypes.string.isRequired, timeStamp: PropTypes.string.isRequired, - })).isRequired + liked: PropTypes.bool.isRequired, + })).isRequired, + onLiked: PropTypes.func.isRequired, }; export default ChatLog; From b71053def7c1dc1940aa5c040fa3485cb3bb6c7a Mon Sep 17 00:00:00 2001 From: Kristina N <87397396+kstinanguyen@users.noreply.github.com> Date: Thu, 12 Dec 2024 16:54:57 -0800 Subject: [PATCH 3/4] implemented optional enhancement: local and remote bubbles --- src/App.css | 4 +++- src/App.jsx | 4 +--- src/components/ChatEntry.jsx | 4 +++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/App.css b/src/App.css index 6ff25f541..6dedb9799 100644 --- a/src/App.css +++ b/src/App.css @@ -23,6 +23,7 @@ font-size: 1.5em; text-align: center; display: inline-block; + padding-top: 0.5em; } #App header section { @@ -36,11 +37,12 @@ color: black; font-size:0.8em; padding: 1em; + font-weight: bold; } #App #heartWidget { font-size: 1.5em; - margin: 1em; + margin: 0.25em; } #App span { diff --git a/src/App.jsx b/src/App.jsx index c7caae800..bcb5ca361 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -21,14 +21,12 @@ const App = () => { return (
-

Chat thread between Name1 and Name2

+

Chatroom: Name1 and Name2

{likedCount} ❤️s
- {/* Wave 01: Render one ChatEntry component - Wave 02: Render ChatLog component */}
diff --git a/src/components/ChatEntry.jsx b/src/components/ChatEntry.jsx index ac3926366..2d500d3bb 100644 --- a/src/components/ChatEntry.jsx +++ b/src/components/ChatEntry.jsx @@ -9,9 +9,11 @@ const ChatEntry = ({ id, sender, body, timeStamp, liked, onLiked }) => { const filledHeart = liked ? '❤️' : '🤍'; + const setMessageBubble = id%2 ? 'local' : 'remote'; + return ( <> -
+

{sender}

{body}

From 8c766cb1d9763891e6869a447b2b2189b04e682e Mon Sep 17 00:00:00 2001 From: Kristina N <87397396+kstinanguyen@users.noreply.github.com> Date: Mon, 16 Dec 2024 00:19:38 -0800 Subject: [PATCH 4/4] additional enhancements for light-dark theme toggle --- src/App.css | 161 ++++++++++++++++++++++++++++--------- src/App.jsx | 12 ++- src/dark-mode/DarkMode.css | 44 ++++++++++ src/dark-mode/DarkMode.jsx | 29 +++++++ 4 files changed, 204 insertions(+), 42 deletions(-) create mode 100644 src/dark-mode/DarkMode.css create mode 100644 src/dark-mode/DarkMode.jsx diff --git a/src/App.css b/src/App.css index 6dedb9799..8c8ae257d 100644 --- a/src/App.css +++ b/src/App.css @@ -1,52 +1,133 @@ #App { - background-color: #87cefa; -} + background-color: white; -#App header { - background-color: #222; - color: #fff; - position: fixed; - width: 100%; - z-index: 100; - text-align: center; - align-items: center; -} + header { + background-color: #e9ecef; + color: black; + position: fixed; + width: 100%; + z-index: 100; + text-align: center; + align-items: center; + } -#App main { - padding-left: 2em; - padding-right: 2em; - padding-bottom: 5rem; - padding-top: 10rem; -} + main { + padding-left: 2em; + padding-right: 2em; + padding-bottom: 5rem; + padding-top: 10rem; + } -#App h1 { - font-size: 1.5em; - text-align: center; - display: inline-block; - padding-top: 0.5em; -} + h1 { + font-size: 1.5em; + text-align: center; + display: inline-block; + padding-top: 0.5em; + } -#App header section { - background-color: #e0ffff; -} + header section { + background-color: #e0ffff; + } -#App .widget { - display: inline-block; - line-height: 0.5em; - border-radius: 10px; - color: black; - font-size:0.8em; - padding: 1em; - font-weight: bold; -} + .widget { + display: inline-block; + line-height: 0.5em; + border-radius: 10px; + color: black; + font-size:0.8em; + padding: 1em; + font-weight: bold; + } + + #heartWidget { + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5em; + margin: 0.25em; + } -#App #heartWidget { - font-size: 1.5em; - margin: 0.25em; + span { + display: inline-block; + } } -#App span { - display: inline-block +#AppDark { + background-color: #343a40; + + header { + background-color: #222; + color: #fff; + position: fixed; + width: 100%; + z-index: 100; + text-align: center; + align-items: center; + } + + main { + padding-left: 2em; + padding-right: 2em; + padding-bottom: 5rem; + padding-top: 10rem; + } + + h1 { + font-size: 1.5em; + text-align: center; + display: inline-block; + padding-top: 0.5em; + } + + h2 { + color: white; + } + + .entry-bubble p { + color: white; + } + + .local .entry-bubble { + background-color: #212529; + } + + .local .entry-bubble::before { + background-color: #212529; + } + + .remote .entry-bubble { + background-color: #2a6f97; + } + + .remote .entry-bubble::before { + background-color: #2a6f97; + } + + header section { + background-color: #00568a; + } + + .widget { + display: inline-block; + line-height: 0.5em; + border-radius: 10px; + color: white; + font-size:0.8em; + padding: 1em; + font-weight: bold; + } + + #heartWidget { + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5em; + margin: 0.25em; + } + + span { + display: inline-block + } } .red { diff --git a/src/App.jsx b/src/App.jsx index bcb5ca361..161a2fdd1 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,9 +2,12 @@ import './App.css'; import CHATS from './data/messages.json'; import ChatLog from './components/ChatLog'; import { useState } from 'react'; +import DarkMode from './dark-mode/DarkMode'; + const App = () => { const [chatData, setChatData] = useState(CHATS); + const [theme, setTheme] = useState('App'); const handleLikedMessages = (id) => { setChatData(chatData => chatData.map(chat => { @@ -16,14 +19,19 @@ const App = () => { })); }; + const toggleDarkMode = (mode) => { + setTheme(mode); + }; + const likedCount = chatData.filter((chat) => chat.liked).length; return ( -
+
-

Chatroom: Name1 and Name2

+

Chatroom: {chatData[0].sender} and {chatData[1].sender}

{likedCount} ❤️s +
diff --git a/src/dark-mode/DarkMode.css b/src/dark-mode/DarkMode.css new file mode 100644 index 000000000..bd4db5b3a --- /dev/null +++ b/src/dark-mode/DarkMode.css @@ -0,0 +1,44 @@ +.dark_mode { + margin-top: -20px; + margin-left: 10px; +} + +.dark_mode_label { + width: 55px; + height: 30px; + position: relative; + display: block; + background: #ebebeb; + border-radius: 200px; + box-shadow: inset 0px 5px 15px rgba(0, 0, 0, 0.4), + inset 0px -5px 15px rgba(255, 255, 255, 0.4); + cursor: pointer; + transition: 0.3s; +} +.dark_mode_label:after { + content: ""; + width: 25px; + height: 25px; + position: absolute; + top: 3px; + left: 5px; + background: linear-gradient(180deg, #f8f9fa, #e9ecef); + border-radius: 180px; + box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.2); + transition: 0.3s; +} +.dark_mode_input { + width: 0; + height: 0; + visibility: hidden; +} +.dark_mode_input:checked + .dark_mode_label { + background: #242424; +} +.dark_mode_input:checked + .dark_mode_label:after { + left: 50px; + transform: translateX(-100%); +} +.dark_mode_label:active:after { + width: 30px; +} diff --git a/src/dark-mode/DarkMode.jsx b/src/dark-mode/DarkMode.jsx new file mode 100644 index 000000000..b62659ec6 --- /dev/null +++ b/src/dark-mode/DarkMode.jsx @@ -0,0 +1,29 @@ +import './DarkMode.css'; +import PropTypes from 'prop-types'; + +const DarkMode = ({ currentTheme, toggleTheme }) => { + let mode = currentTheme === 'App' ? 'AppDark' : 'App'; + + return ( +
+ + +
+ ); +}; + +DarkMode.propTypes = { + currentTheme: PropTypes.string.isRequired, + toggleTheme: PropTypes.func.isRequired +}; + +export default DarkMode;