Skip to content

Commit 57b100b

Browse files
authored
Input for labels (#338)
* wip * chore: improve labels in light mode * feat: add dropdown support * chore: remove comments * chore: update visual snapshots * chore: remome some exports * chore: fix add new note * fix: label dark bg color for filled variant * fix: optimize use of setter in callbacks * fix: re-attach onclick event properly * fix: fix typo * fix: improve placeholder * fix: update snapshots
1 parent 3f3e485 commit 57b100b

39 files changed

+323
-127
lines changed

src/components/Label/Label.css

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/components/Label/Label.tsx

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1+
import { type ReactNode, useMemo } from "react";
12
import { twMerge } from "tailwind-merge";
23
import { contrast, hslColorToCss, hslToHex } from "./color-utils";
3-
import "./Label.css";
4-
import { useMemo } from "react";
54

65
export function Label({
76
label,
8-
icon = "",
7+
endSlot,
98
className = "",
109
variant = "filled",
1110
showTooltip = false,
1211
}: {
1312
label: string;
14-
icon?: string;
13+
endSlot?: ReactNode;
1514
className?: string;
1615
variant?: "filled" | "outlined";
1716
showTooltip?: boolean;
@@ -22,29 +21,34 @@ export function Label({
2221

2322
const style = useMemo(
2423
() => ({
25-
backgroundColor: variant === "filled" ? mainColor.hex : hslColorToCss(dimmedMainColor.hsl),
26-
color: variant === "filled" ? contrastMainColor : mainColor.hex,
24+
"--dark-bg-color": hslColorToCss(variant === "filled" ? mainColor.hsl : dimmedMainColor.dark),
25+
"--light-bg-color": hslColorToCss(variant === "filled" ? mainColor.hsl : dimmedMainColor.light),
26+
"--dark-text-color": variant === "filled" ? contrastMainColor : mainColor.hex,
27+
"--light-text-color": variant === "filled" ? contrastMainColor : "#000000",
2728
border: variant === "filled" ? "1px solid transparent" : `1px solid ${mainColor.hex}`,
2829
}),
29-
[dimmedMainColor, contrastMainColor, variant, mainColor],
30+
[contrastMainColor, variant, mainColor, dimmedMainColor],
3031
);
3132

3233
return (
3334
<span
3435
style={style}
35-
className={twMerge("label truncate rounded-xl px-2.5 py-0.5", className)}
36+
className={twMerge(
37+
"label inline-flex items-center min-w-0 max-w-full rounded-xl px-2.5 py-0.5 bg-[var(--light-bg-color)] dark:bg-[var(--dark-bg-color)] text-[var(--light-text-color)] dark:text-[var(--dark-text-color)]",
38+
className,
39+
)}
3640
title={showTooltip ? label : undefined}
3741
>
38-
{icon} {label}
42+
<span className="truncate">{label}</span>
43+
{endSlot}
3944
</span>
4045
);
4146
}
4247

4348
function dimColor(mainColor: { hsl: [number, number, number] }) {
44-
const newDimmedSaturation = mainColor.hsl[1] * 0.75;
45-
const newDimmedLightness = mainColor.hsl[2] / 5;
46-
const newHsl = [mainColor.hsl[0], newDimmedSaturation, newDimmedLightness] as [number, number, number];
47-
return { hsl: newHsl };
49+
const darkColor = [mainColor.hsl[0], mainColor.hsl[1] * 0.75, mainColor.hsl[2] / 5] as [number, number, number];
50+
const lightColor = [mainColor.hsl[0], 80, 90] as [number, number, number];
51+
return { dark: darkColor, light: lightColor };
4852
}
4953

5054
function bestTextColor(bgHex: string) {

src/components/NoteManager/NoteManager.css

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,6 @@
2424
}
2525

2626

27-
.note-manager .new-note button {
28-
width: 100%;
29-
}
30-
31-
.note-manager textarea,
32-
.note-manager input {
33-
padding: 1rem;
34-
resize: vertical;
35-
width: 100%;
36-
box-sizing: border-box;
37-
border-radius: 6px;
38-
}
39-
4027
.note-manager .validation-message {
4128
padding-bottom: 10px;
4229
color: #f22;

src/components/NoteManager/NoteManager.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { memo, useCallback, useContext, useEffect, useState } from "react";
22
import "./NoteManager.css";
3+
import { Button, Textarea } from "@fluffylabs/shared-ui";
34
import { twMerge } from "tailwind-merge";
45
import { validateMath } from "../../utils/validateMath";
56
import { type ILocationContext, LocationContext } from "../LocationProvider/LocationProvider";
@@ -75,8 +76,8 @@ function Notes() {
7576

7677
return (
7778
<div className="note-manager flex flex-col gap-2.5" style={{ opacity: notesReady ? 1.0 : 0.3 }}>
78-
<div className="new-note">
79-
<textarea
79+
<div className="flex flex-col p-2 gap-2">
80+
<Textarea
8081
disabled={selectedBlocks.length === 0}
8182
className={noteContentError ? "error" : ""}
8283
autoFocus
@@ -86,9 +87,9 @@ function Notes() {
8687
/>
8788

8889
{noteContentError ? <div className="validation-message">{noteContentError}</div> : null}
89-
<button disabled={noteContent.length < 1} onClick={handleAddNoteClick} className="default-button">
90+
<Button disabled={noteContent.length < 1} onClick={handleAddNoteClick} variant="secondary">
9091
Add
91-
</button>
92+
</Button>
9293
</div>
9394

9495
<MemoizedNotesList

src/components/NoteManager/components/Note.css

Whitespace-only changes.

src/components/NoteManager/components/Note.tsx

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1+
import { Button, cn } from "@fluffylabs/shared-ui";
12
import { type ChangeEvent, useCallback, useEffect, useMemo, useState } from "react";
23
import { validateMath } from "../../../utils/validateMath";
4+
import { useLocationContext } from "../../LocationProvider/LocationProvider";
35
import type { INotesContext } from "../../NotesProvider/NotesProvider";
46
import { type IDecoratedNote, NoteSource } from "../../NotesProvider/types/DecoratedNote";
57
import type { IStorageNote } from "../../NotesProvider/types/StorageNote";
6-
import { NoteLabels } from "./NoteLabels";
7-
import { NoteLink } from "./NoteLink";
8-
import "./Note.css";
9-
import { Button, cn } from "@fluffylabs/shared-ui";
10-
import { useLocationContext } from "../../LocationProvider/LocationProvider";
118
import { NoteLayout } from "./NoteLayout";
9+
import { NoteLink } from "./NoteLink";
10+
import { TinyIconButton } from "./SiimpleComponents";
1211

1312
export type NotesItem = {
1413
location: string; // serialized InDocSelection
@@ -55,12 +54,13 @@ export function Note({ note, active = false, onEditNote, onDeleteNote }: NotePro
5554
setNoteContentError("");
5655
}, [note, isEditable]);
5756

58-
const handleNoteContentChange = useCallback(
59-
(ev: ChangeEvent<HTMLTextAreaElement>) => {
60-
setNoteDirty({ ...noteDirty, content: ev.currentTarget.value });
61-
},
62-
[noteDirty],
63-
);
57+
const handleNoteLabelsChange = useCallback((labels: string[]) => {
58+
setNoteDirty((prevNoteDirty) => ({ ...prevNoteDirty, labels }));
59+
}, []);
60+
61+
const handleNoteContentChange = useCallback((ev: ChangeEvent<HTMLTextAreaElement>) => {
62+
setNoteDirty((prev) => ({ ...prev, content: ev.currentTarget.value }));
63+
}, []);
6464

6565
const handleDeleteClick = useCallback(() => {
6666
onDeleteNote(note);
@@ -128,6 +128,7 @@ export function Note({ note, active = false, onEditNote, onDeleteNote }: NotePro
128128
isEditing,
129129
noteDirty,
130130
handleNoteContentChange,
131+
handleNoteLabelsChange,
131132
}),
132133
[
133134
note,
@@ -139,6 +140,7 @@ export function Note({ note, active = false, onEditNote, onDeleteNote }: NotePro
139140
isEditing,
140141
noteDirty,
141142
handleNoteContentChange,
143+
handleNoteLabelsChange,
142144
],
143145
);
144146

@@ -166,20 +168,11 @@ export function Note({ note, active = false, onEditNote, onDeleteNote }: NotePro
166168
{active && !isEditing && (
167169
<>
168170
<NoteLayout.SelectedText />
169-
<NoteLabels note={note} />
170171
<NoteLayout.Text />
172+
<NoteLayout.Labels />
171173
{isEditable && (
172174
<div className="flex flex-1 justify-end">
173-
<Button
174-
variant="ghost"
175-
intent="neutralStrong"
176-
className="p-2 h-6 -top-0.5 relative"
177-
data-testid={"edit-button"}
178-
onClick={handleEditClick}
179-
aria-label="Edit note"
180-
>
181-
✏️
182-
</Button>
175+
<TinyIconButton data-testid={"edit-button"} onClick={handleEditClick} aria-label="Edit note" icon="✏️" />
183176
</div>
184177
)}
185178
</>
@@ -188,9 +181,9 @@ export function Note({ note, active = false, onEditNote, onDeleteNote }: NotePro
188181
<>
189182
<>
190183
<NoteLayout.SelectedText />
191-
<NoteLabels note={note} />
192184
<NoteLayout.TextArea className={noteContentError ? "error" : ""} />
193185
{noteContentError ? <div className="validation-message">{noteContentError}</div> : null}
186+
<NoteLayout.Labels />
194187
<div className="actions gap-2">
195188
<Button variant="ghost" intent="destructive" size="sm" onClick={handleDeleteClick}>
196189
Delete

src/components/NoteManager/components/NoteContext.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type ChangeEvent, createContext, useContext } from "react";
22
import type { INotesContext } from "../../NotesProvider/NotesProvider";
33
import type { IDecoratedNote } from "../../NotesProvider/types/DecoratedNote";
4+
import type { IStorageNote } from "../../NotesProvider/types/StorageNote";
45

56
export const noteContext = createContext<{
67
note: IDecoratedNote;
@@ -9,7 +10,8 @@ export const noteContext = createContext<{
910
handleSaveClick: () => void;
1011
handleCancelClick: () => void;
1112
handleNoteContentChange: (ev: ChangeEvent<HTMLTextAreaElement>) => void;
12-
noteDirty: { content: string };
13+
handleNoteLabelsChange: (labels: string[]) => void;
14+
noteDirty: IStorageNote;
1315
onEditNote: INotesContext["handleUpdateNote"];
1416
isEditing: boolean;
1517
} | null>(null);

0 commit comments

Comments
 (0)