mirror of
https://github.com/remvze/moodist.git
synced 2025-12-17 17:04:15 +00:00
feat: add simple notepad
This commit is contained in:
parent
38c11f124e
commit
e923559709
15 changed files with 157 additions and 5 deletions
|
|
@ -4,7 +4,8 @@
|
|||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
padding: 16px 12px;
|
||||
height: 50px;
|
||||
padding: 0 12px;
|
||||
font-size: var(--font-sm);
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export { Shuffle as ShuffleItem } from './shuffle';
|
||||
export { Share as ShareItem } from './share';
|
||||
export { Donate as DonateItem } from './donate';
|
||||
export { Notepad as NotepadItem } from './notepad';
|
||||
|
|
|
|||
11
src/components/menu/items/notepad.tsx
Normal file
11
src/components/menu/items/notepad.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { MdNotes } from 'react-icons/md/index';
|
||||
|
||||
import { Item } from '../item';
|
||||
|
||||
interface NotepadProps {
|
||||
open: () => void;
|
||||
}
|
||||
|
||||
export function Notepad({ open }: NotepadProps) {
|
||||
return <Item icon={<MdNotes />} label="Notepad" onClick={open} />;
|
||||
}
|
||||
|
|
@ -14,8 +14,9 @@ import {
|
|||
FloatingFocusManager,
|
||||
} from '@floating-ui/react';
|
||||
|
||||
import { ShuffleItem, ShareItem, DonateItem } from './items';
|
||||
import { ShuffleItem, ShareItem, DonateItem, NotepadItem } from './items';
|
||||
import { ShareLinkModal } from '@/components/modals/share-link';
|
||||
import { Notepad } from '@/components/toolbox';
|
||||
|
||||
import { slideY, fade, mix } from '@/lib/motion';
|
||||
|
||||
|
|
@ -25,6 +26,7 @@ export function Menu() {
|
|||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const [showShareLink, setShowShareLink] = useState(false);
|
||||
const [showNotepad, setShowNotepad] = useState(false);
|
||||
|
||||
const variants = mix(slideY(-20), fade());
|
||||
|
||||
|
|
@ -76,6 +78,7 @@ export function Menu() {
|
|||
>
|
||||
<ShareItem open={() => setShowShareLink(true)} />
|
||||
<ShuffleItem />
|
||||
<NotepadItem open={() => setShowNotepad(true)} />
|
||||
<DonateItem />
|
||||
</motion.div>
|
||||
</div>
|
||||
|
|
@ -88,6 +91,8 @@ export function Menu() {
|
|||
show={showShareLink}
|
||||
onClose={() => setShowShareLink(false)}
|
||||
/>
|
||||
|
||||
<Notepad show={showNotepad} onClose={() => setShowNotepad(false)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,13 @@
|
|||
background-color: var(--color-neutral-100);
|
||||
border-radius: 8px;
|
||||
|
||||
&.wide {
|
||||
width: 95%;
|
||||
max-width: 600px;
|
||||
padding: 12px;
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
& .close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { AnimatePresence, motion } from 'framer-motion';
|
|||
import { IoClose } from 'react-icons/io5/index';
|
||||
|
||||
import { fade, mix, slideY } from '@/lib/motion';
|
||||
import { cn } from '@/helpers/styles';
|
||||
|
||||
import styles from './modal.module.css';
|
||||
|
||||
|
|
@ -9,9 +10,10 @@ interface ModalProps {
|
|||
children: React.ReactNode;
|
||||
onClose: () => void;
|
||||
show: boolean;
|
||||
wide?: boolean;
|
||||
}
|
||||
|
||||
export function Modal({ children, onClose, show }: ModalProps) {
|
||||
export function Modal({ children, onClose, show, wide }: ModalProps) {
|
||||
const variants = {
|
||||
modal: mix(fade(), slideY(20)),
|
||||
overlay: fade(),
|
||||
|
|
@ -33,7 +35,7 @@ export function Modal({ children, onClose, show }: ModalProps) {
|
|||
<div className={styles.modal}>
|
||||
<motion.div
|
||||
animate="show"
|
||||
className={styles.content}
|
||||
className={cn(styles.content, wide && styles.wide)}
|
||||
exit="hidden"
|
||||
initial="hidden"
|
||||
variants={variants.modal}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useEffect } from 'react';
|
||||
|
||||
import { useSoundStore } from '@/store';
|
||||
import { useSoundStore, useNoteStore } from '@/store';
|
||||
|
||||
interface StoreConsumerProps {
|
||||
children: React.ReactNode;
|
||||
|
|
@ -9,6 +9,7 @@ interface StoreConsumerProps {
|
|||
export function StoreConsumer({ children }: StoreConsumerProps) {
|
||||
useEffect(() => {
|
||||
useSoundStore.persist.rehydrate();
|
||||
useNoteStore.persist.rehydrate();
|
||||
}, []);
|
||||
|
||||
return <>{children}</>;
|
||||
|
|
|
|||
1
src/components/toolbox/index.ts
Normal file
1
src/components/toolbox/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { Notepad } from './notepad';
|
||||
1
src/components/toolbox/notepad/index.ts
Normal file
1
src/components/toolbox/notepad/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { Notepad } from './notepad';
|
||||
26
src/components/toolbox/notepad/notepad.module.css
Normal file
26
src/components/toolbox/notepad/notepad.module.css
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
|
||||
& .label {
|
||||
font-size: var(--font-sm);
|
||||
font-weight: 500;
|
||||
color: var(--color-foreground-subtle);
|
||||
}
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
height: 350px;
|
||||
padding: 12px;
|
||||
line-height: 1.6;
|
||||
color: var(--color-foreground-subtle);
|
||||
resize: none;
|
||||
background-color: var(--color-neutral-50);
|
||||
border: 1px solid var(--color-neutral-200);
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
scroll-padding-bottom: 12px;
|
||||
}
|
||||
29
src/components/toolbox/notepad/notepad.tsx
Normal file
29
src/components/toolbox/notepad/notepad.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { Modal } from '@/components/modal';
|
||||
|
||||
import styles from './notepad.module.css';
|
||||
import { useNoteStore } from '@/store';
|
||||
|
||||
interface NotepadProps {
|
||||
onClose: () => void;
|
||||
show: boolean;
|
||||
}
|
||||
|
||||
export function Notepad({ onClose, show }: NotepadProps) {
|
||||
const note = useNoteStore(state => state.note);
|
||||
const write = useNoteStore(state => state.write);
|
||||
|
||||
return (
|
||||
<Modal show={show} wide onClose={onClose}>
|
||||
<header className={styles.header}>
|
||||
<h2 className={styles.label}>Your Note</h2>
|
||||
</header>
|
||||
|
||||
<textarea
|
||||
className={styles.textarea}
|
||||
dir="auto"
|
||||
value={note}
|
||||
onChange={e => write(e.target.value)}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
export { useSoundStore } from './sound';
|
||||
export { useLoadingStore } from './loading';
|
||||
export { useNoteStore } from './note';
|
||||
|
|
|
|||
28
src/store/note/index.ts
Normal file
28
src/store/note/index.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { create } from 'zustand';
|
||||
import { createJSONStorage, persist } from 'zustand/middleware';
|
||||
import merge from 'deepmerge';
|
||||
|
||||
import { type NoteState, createState } from './note.state';
|
||||
import { type NoteActions, createActions } from './note.actions';
|
||||
|
||||
export const useNoteStore = create<NoteState & NoteActions>()(
|
||||
persist(
|
||||
(...a) => ({
|
||||
...createState(...a),
|
||||
...createActions(...a),
|
||||
}),
|
||||
{
|
||||
merge: (persisted, current) =>
|
||||
merge(
|
||||
current,
|
||||
// @ts-ignore
|
||||
persisted,
|
||||
),
|
||||
name: 'moodist-note',
|
||||
partialize: state => ({ note: state.note }),
|
||||
skipHydration: true,
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
version: 0,
|
||||
},
|
||||
),
|
||||
);
|
||||
20
src/store/note/note.actions.ts
Normal file
20
src/store/note/note.actions.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import type { StateCreator } from 'zustand';
|
||||
|
||||
import type { NoteState } from './note.state';
|
||||
|
||||
export interface NoteActions {
|
||||
write: (note: string) => void;
|
||||
}
|
||||
|
||||
export const createActions: StateCreator<
|
||||
NoteActions & NoteState,
|
||||
[],
|
||||
[],
|
||||
NoteActions
|
||||
> = set => {
|
||||
return {
|
||||
write(note) {
|
||||
set({ note });
|
||||
},
|
||||
};
|
||||
};
|
||||
18
src/store/note/note.state.ts
Normal file
18
src/store/note/note.state.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import type { StateCreator } from 'zustand';
|
||||
|
||||
import type { NoteActions } from './note.actions';
|
||||
|
||||
export interface NoteState {
|
||||
history: string | null;
|
||||
note: string;
|
||||
}
|
||||
|
||||
export const createState: StateCreator<
|
||||
NoteState & NoteActions,
|
||||
[],
|
||||
[],
|
||||
NoteState
|
||||
> = () => ({
|
||||
history: null,
|
||||
note: '',
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue