From bc731ea2b8592e376c5ace9e3c3679cde747cd4f Mon Sep 17 00:00:00 2001 From: louiscklaw Date: Sun, 8 Jun 2025 19:07:48 +0800 Subject: [PATCH] update demo-react-notes, --- .../components/ExploreContainer.css | 24 +++ .../components/ExploreContainer.tsx | 21 +++ .../mobile/src/pages/DemoReactNotes/index.tsx | 35 ++-- .../DemoReactNotes/pages/Add.module.scss | 29 +++ .../src/pages/DemoReactNotes/pages/Add.tsx | 115 ++++++++++++ .../DemoReactNotes/pages/Home.module.scss | 130 ++++++++++++++ .../src/pages/DemoReactNotes/pages/Home.tsx | 166 ++++++++++++++++++ .../DemoReactNotes/store/CategoryStore.ts | 33 ++++ .../pages/DemoReactNotes/store/NoteStore.ts | 58 ++++++ .../pages/DemoReactNotes/store/Selectors.ts | 16 ++ .../src/pages/DemoReactNotes/store/index.js | 2 + .../src/pages/DemoReactNotes/style.scss | 103 ----------- .../pages/DemoReactNotes/theme/variables.scss | 0 13 files changed, 606 insertions(+), 126 deletions(-) create mode 100644 03_source/mobile/src/pages/DemoReactNotes/components/ExploreContainer.css create mode 100644 03_source/mobile/src/pages/DemoReactNotes/components/ExploreContainer.tsx create mode 100644 03_source/mobile/src/pages/DemoReactNotes/pages/Add.module.scss create mode 100644 03_source/mobile/src/pages/DemoReactNotes/pages/Add.tsx create mode 100644 03_source/mobile/src/pages/DemoReactNotes/pages/Home.module.scss create mode 100644 03_source/mobile/src/pages/DemoReactNotes/pages/Home.tsx create mode 100644 03_source/mobile/src/pages/DemoReactNotes/store/CategoryStore.ts create mode 100644 03_source/mobile/src/pages/DemoReactNotes/store/NoteStore.ts create mode 100644 03_source/mobile/src/pages/DemoReactNotes/store/Selectors.ts create mode 100644 03_source/mobile/src/pages/DemoReactNotes/store/index.js delete mode 100644 03_source/mobile/src/pages/DemoReactNotes/style.scss create mode 100644 03_source/mobile/src/pages/DemoReactNotes/theme/variables.scss diff --git a/03_source/mobile/src/pages/DemoReactNotes/components/ExploreContainer.css b/03_source/mobile/src/pages/DemoReactNotes/components/ExploreContainer.css new file mode 100644 index 0000000..e99f514 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactNotes/components/ExploreContainer.css @@ -0,0 +1,24 @@ +.container { + text-align: center; + position: absolute; + left: 0; + right: 0; + top: 50%; + transform: translateY(-50%); +} + +.container strong { + font-size: 20px; + line-height: 26px; +} + +.container p { + font-size: 16px; + line-height: 22px; + color: #8c8c8c; + margin: 0; +} + +.container a { + text-decoration: none; +} \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoReactNotes/components/ExploreContainer.tsx b/03_source/mobile/src/pages/DemoReactNotes/components/ExploreContainer.tsx new file mode 100644 index 0000000..7f14436 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactNotes/components/ExploreContainer.tsx @@ -0,0 +1,21 @@ +import './ExploreContainer.css'; + +const ExploreContainer = (): React.JSX.Element => { + return ( +
+ Ready to create an app? +

+ Start with Ionic{' '} + + UI Components + +

+
+ ); +}; + +export default ExploreContainer; diff --git a/03_source/mobile/src/pages/DemoReactNotes/index.tsx b/03_source/mobile/src/pages/DemoReactNotes/index.tsx index ad6dd84..8891e53 100644 --- a/03_source/mobile/src/pages/DemoReactNotes/index.tsx +++ b/03_source/mobile/src/pages/DemoReactNotes/index.tsx @@ -1,38 +1,27 @@ -import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react'; +import { IonRouterOutlet, IonTabs } from '@ionic/react'; -import { cloudOutline, searchOutline } from 'ionicons/icons'; import { Route, Redirect } from 'react-router'; -import Tab1 from './AppPages/Tab1'; -import Tab2 from './AppPages/Tab2'; +// import Tab1 from './AppPages/Tab1'; +// import Tab2 from './AppPages/Tab2'; -import './style.scss'; +import './theme/variables.scss'; +import Home from './pages/Home'; +import Add from './pages/Add'; function DemoReactNotes() { return ( - + - - + + - - + + - + - - {/* */} - - - - Dashboard - - - - Search - - ); } diff --git a/03_source/mobile/src/pages/DemoReactNotes/pages/Add.module.scss b/03_source/mobile/src/pages/DemoReactNotes/pages/Add.module.scss new file mode 100644 index 0000000..490a0ea --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactNotes/pages/Add.module.scss @@ -0,0 +1,29 @@ +.title { + + margin-top: 0.35rem; +} + +.customInput { + + border-radius: 22px !important; + --padding-bottom: 1rem; + --padding-top: 1rem; + + box-shadow: 0 0.7px 0.9px rgba(0, 0, 0, 0.101), + 0 0.9px 2.5px rgba(0, 0, 0, 0.145), + 0 2.5px 6px rgba(0, 0, 0, 0.189), + 0 8px 20px rgba(0, 0, 0, 0.29); +} + +.customInput { + + margin-bottom: 1rem !important; +} + +.saveButton { + + --border-radius: 12px !important; + + --padding-top: 1.75rem !important; + --padding-bottom: 1.75rem !important; +} \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoReactNotes/pages/Add.tsx b/03_source/mobile/src/pages/DemoReactNotes/pages/Add.tsx new file mode 100644 index 0000000..4d58927 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactNotes/pages/Add.tsx @@ -0,0 +1,115 @@ +import { + IonBackButton, + IonButton, + IonButtons, + IonCol, + IonContent, + IonGrid, + IonHeader, + IonIcon, + IonItem, + IonLabel, + IonPage, + IonRow, + IonSelect, + IonSelectOption, + IonTextarea, + IonTitle, + IonToolbar, + useIonRouter, +} from '@ionic/react'; +import styles from './Add.module.scss'; + +import { checkmarkOutline } from 'ionicons/icons'; +import { getCategories, getNotes } from '../store/Selectors'; +import { CategoryStore, NoteStore } from '../store'; +import { addNote } from '../store/NoteStore'; +import { useState } from 'react'; + +const Add = (): React.JSX.Element => { + const categories = CategoryStore.useState(getCategories); + const notes = NoteStore.useState(getNotes); + const [noteCategory, setNoteCategory] = useState(false); + const [noteContent, setNoteContent] = useState(''); + const router = useIonRouter(); + + const add = () => { + const note = { + id: notes.length + 1, + category_id: noteCategory, + note: noteContent, + complete: false, + }; + + addNote(note); + router.goBack(); + }; + + return ( + + + + + + + + Add note + + + + + + +

Add a note

+
+
+ + + + + Category + setNoteCategory(e.target.value)} + > + {categories.map((category) => { + return ( + + {category.name} + + ); + })} + + + + + + + + + Note + setNoteContent(e.target.value)} + placeholder="Enter note text here..." + /> + + + + + + + + + Save note + + + +
+
+
+ ); +}; + +export default Add; diff --git a/03_source/mobile/src/pages/DemoReactNotes/pages/Home.module.scss b/03_source/mobile/src/pages/DemoReactNotes/pages/Home.module.scss new file mode 100644 index 0000000..ec7c818 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactNotes/pages/Home.module.scss @@ -0,0 +1,130 @@ +.heading { + + color: var(--lighter-blue-color); + font-size: 1rem; +} + +.mainTitle { + + font-size: 2rem; + font-weight: 700; +} + +.slideHeader { + + margin: 0 !important; + padding: 0; + padding: 1.5rem !important; + padding-top: 0 !important; +} + +.slideCount { + + margin: 0 !important; + padding: 0 !important; + padding-left: 1.5rem !important; + padding-top: 1rem !important; +} + +.slideCount h6 { + + color: var(--light-blue-color); +} + +.slideHeader h4 { + + color: white; + margin: 0 !important; + padding: 0 !important; + text-align: left; + font-size: 1.75rem; +} + +.categorySlider { + + margin-top: -1.6rem; + + ion-slide { + + width: 60%; + margin-right: 30px; + } + + ion-col { + + padding-left: 0; + + ion-card { + + width: 100%; + border-radius: 22px; + // box-shadow: 0px 4px 12px 2px rgba(0, 0, 0, 0.16); + + box-shadow: 0 0.7px 0.9px rgba(0, 0, 0, 0.101), + 0 1.9px 2.5px rgba(0, 0, 0, 0.145), + 0 4.5px 6px rgba(0, 0, 0, 0.189), + 0 15px 20px rgba(0, 0, 0, 0.29); + + ion-card-header { + + text-align: left !important; + } + + ion-card-content { + + padding: 0; + padding: 0.5rem; + } + } + } + + .lastUsed { + + display: flex; + flex-direction: row; + align-content: flex-start; + align-items: center; + justify-content: center; + font-size: 0.9rem; + color: white; + } + + .categoryColor { + + border-bottom: 3px solid var(--pink-color); + margin-left: 1rem; + margin-right: 1rem; + margin-bottom: 0.5rem; + margin-top: -1rem; + } +} + +.recentNotes { + + padding-left: 0.5rem; + padding-right: 0.5rem; + max-height: 18.8rem; + overflow: scroll; + + ion-item { + + --padding-start: 2rem; + --padding-end: 2rem; + --border-style: none; + --border-radius: 22px; + --min-height: 4rem; + + h4 { + + padding-left: 1rem; + margin-top: 0.6rem; + font-size: 1.2rem; + font-weight: 400; + } + } +} + +.bottomContainer { + + margin-top: -1rem; +} \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoReactNotes/pages/Home.tsx b/03_source/mobile/src/pages/DemoReactNotes/pages/Home.tsx new file mode 100644 index 0000000..337108b --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactNotes/pages/Home.tsx @@ -0,0 +1,166 @@ +import { + IonButton, + IonButtons, + IonCard, + IonCardContent, + IonCardHeader, + IonCardSubtitle, + IonCheckbox, + IonCol, + IonContent, + IonFab, + IonFabButton, + IonGrid, + IonHeader, + IonIcon, + IonItem, + IonPage, + IonRow, + // IonSlide, + // IonSlides, + IonToolbar, +} from '@ionic/react'; +import styles from './Home.module.scss'; + +import { addOutline, menuOutline, notificationsOutline, searchOutline } from 'ionicons/icons'; +import { getCategories, getNotes } from '../store/Selectors'; +import { CategoryStore, NoteStore } from '../store'; +import { markNote } from '../store/NoteStore'; + +const Home = (): React.JSX.Element => { + return <>TODO: need update IonSlide; + + const categories = CategoryStore.useState(getCategories); + const notes = NoteStore.useState(getNotes); + + const getNoteStyle = (categoryID, isComplete = false) => { + const categoryColor = categories.filter((category) => category.id === categoryID)[0].color; + + return { + '--background': categoryColor, + '--background-checked': categoryColor, + '--border-style': 'none', + opacity: isComplete ? '0.6' : '1', + }; + }; + + return ( + + + + + + + + + + + + + + + + + + + + + + + + +

Hello, Alan!

+
+
+ + + + Categories + + +
+ + + {categories.map((category, index) => { + const noteCount = notes.filter((n) => n.category_id === category.id).length; + + return ( + + + + +
+
+ {noteCount} {noteCount === 1 ? 'note' : 'notes'}{' '} +
+
+
+

{category.name}

+
+
+ + +
+
+
+
+
+ ); + })} +
+ + + + + Recent Notes + + + +
+ {notes.map((note, index) => { + return ( + + + + markNote(note.id)} + /> +

+ {note.note} +

+
+
+
+ ); + })} +
+
+ + + + + + +
+
+ ); +}; + +export default Home; diff --git a/03_source/mobile/src/pages/DemoReactNotes/store/CategoryStore.ts b/03_source/mobile/src/pages/DemoReactNotes/store/CategoryStore.ts new file mode 100644 index 0000000..148b5b7 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactNotes/store/CategoryStore.ts @@ -0,0 +1,33 @@ +import { Store } from 'pullstate'; + +interface Category { + id: number; + name: string; + count: string; + color: string; +} + +const CategoryStore = new Store({ + categories: [ + { + id: 1, + name: 'Business', + count: '34', + color: '#60b660', + }, + { + id: 2, + name: 'Personal', + count: '12', + color: '#1D68DF', + }, + { + id: 3, + name: 'Leisure', + count: '23', + color: '#EB06FF', + }, + ] as Category[], +}); + +export default CategoryStore; diff --git a/03_source/mobile/src/pages/DemoReactNotes/store/NoteStore.ts b/03_source/mobile/src/pages/DemoReactNotes/store/NoteStore.ts new file mode 100644 index 0000000..b60c9ef --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactNotes/store/NoteStore.ts @@ -0,0 +1,58 @@ +import { Store } from 'pullstate'; + +interface Note { + id: number; + category_id: number; + note: string; + complete: boolean; +} + +const NoteStore = new Store({ + notes: [ + { + id: 1, + category_id: 1, + note: 'Daily meeting with team', + complete: false, + }, + { + id: 2, + category_id: 2, + note: 'Pay monthly rent', + complete: true, + }, + { + id: 3, + category_id: 3, + note: 'Workout in the gym', + complete: false, + }, + { + id: 4, + category_id: 1, + note: 'Make progress on project', + complete: false, + }, + ] as Note[], +}); + +export const markNote = (noteID: number) => { + const noteIndex = NoteStore.currentState.notes.findIndex((n) => n.id === noteID); + NoteStore.update((s) => { + s.notes[noteIndex].complete = !s.notes[noteIndex].complete; + }); + + document.getElementById(`noteRow_${noteID}`).classList.add('animate__pulse'); + + setTimeout(() => { + document.getElementById(`noteRow_${noteID}`).classList.remove('animate__pulse'); + }, 500); +}; + +export const addNote = (note: Note) => { + NoteStore.update((s) => { + s.notes = [note, ...s.notes]; + }); +}; + +export default NoteStore; diff --git a/03_source/mobile/src/pages/DemoReactNotes/store/Selectors.ts b/03_source/mobile/src/pages/DemoReactNotes/store/Selectors.ts new file mode 100644 index 0000000..e997fd6 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactNotes/store/Selectors.ts @@ -0,0 +1,16 @@ +import { createSelector } from 'reselect'; +import { Category, Note } from './NoteStore'; + +interface State { + categories: Category[]; + notes: Note[]; +} + +const getState = (state: State) => state; + +// General getters +export const getCategories = createSelector(getState, (state) => state.categories); +export const getNotes = createSelector(getState, (state) => state.notes); + +// More specific getters +// export const getCoffee = (id: number) => createSelector(getState, state => state.coffees.filter(c => parseInt(c.id) === parseInt(id))[0]); diff --git a/03_source/mobile/src/pages/DemoReactNotes/store/index.js b/03_source/mobile/src/pages/DemoReactNotes/store/index.js new file mode 100644 index 0000000..3ede6a0 --- /dev/null +++ b/03_source/mobile/src/pages/DemoReactNotes/store/index.js @@ -0,0 +1,2 @@ +export { default as CategoryStore } from "./CategoryStore"; +export { default as NoteStore } from "./NoteStore"; \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoReactNotes/style.scss b/03_source/mobile/src/pages/DemoReactNotes/style.scss deleted file mode 100644 index 37c1e1a..0000000 --- a/03_source/mobile/src/pages/DemoReactNotes/style.scss +++ /dev/null @@ -1,103 +0,0 @@ -#about-page { - ion-toolbar { - position: absolute; - - top: 0; - left: 0; - right: 0; - - --background: transparent; - --color: white; - } - - ion-toolbar ion-back-button, - ion-toolbar ion-button, - ion-toolbar ion-menu-button { - --color: white; - } - - .about-header { - position: relative; - - width: 100%; - height: 30%; - } - - .about-header .about-image { - position: absolute; - - top: 0; - left: 0; - bottom: 0; - right: 0; - - background-position: center; - background-size: cover; - background-repeat: no-repeat; - - opacity: 0; - - transition: opacity 500ms ease-in-out; - } - - .about-header .madison { - background-image: url('/assets/WeatherDemo/img/about/madison.jpg'); - } - - .about-header .austin { - background-image: url('/assets/WeatherDemo/img/about/austin.jpg'); - } - - .about-header .chicago { - background-image: url('/assets/WeatherDemo/img/about/chicago.jpg'); - } - - .about-header .seattle { - background-image: url('/assets/WeatherDemo/img/about/seattle.jpg'); - } - - .about-info { - position: relative; - margin-top: -10px; - border-radius: 10px; - background: var(--ion-background-color, #fff); - z-index: 2; // display rounded border above header image - } - - .about-info h3 { - margin-top: 0; - } - - .about-info ion-list { - padding-top: 0; - } - - .about-info p { - line-height: 130%; - - color: var(--ion-color-dark); - } - - .about-info ion-icon { - margin-inline-end: 32px; - } - - /* - * iOS Only - */ - - .ios .about-info { - --ion-padding: 19px; - } - - .ios .about-info h3 { - font-weight: 700; - } -} - -#date-input-popover { - --offset-y: -var(--ion-safe-area-bottom); - - --max-width: 90%; - --width: 336px; -} diff --git a/03_source/mobile/src/pages/DemoReactNotes/theme/variables.scss b/03_source/mobile/src/pages/DemoReactNotes/theme/variables.scss new file mode 100644 index 0000000..e69de29