Checklists in the body, colour from tags, and commit-derived CalVer #4
@@ -114,10 +114,14 @@ fun resolvedLabelColor(
|
||||
fun resolvedNoteColor(
|
||||
id: String,
|
||||
color: String,
|
||||
labelColor: String,
|
||||
known: Set<String>,
|
||||
): String =
|
||||
when {
|
||||
color.isNotEmpty() && color != "default" && color in known -> color
|
||||
// The note's FIRST tag. Passed in already resolved, so this does not need to
|
||||
// know that a colourless tag derives one from its name.
|
||||
labelColor.isNotEmpty() -> labelColor
|
||||
// A draft carries DRAFT_ID (""), so there is no identity to derive from yet.
|
||||
// Staying white until the note exists costs one colour change at save time;
|
||||
// hashing the empty string instead would give EVERY draft the same tint and
|
||||
@@ -125,3 +129,18 @@ fun resolvedNoteColor(
|
||||
id.isEmpty() -> "default"
|
||||
else -> derivedTint(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a note's colour was CHOSEN rather than derived — which is what decides
|
||||
* between the two weights the card is drawn at.
|
||||
*
|
||||
* A tag (or, until step 5, the picker) means somebody said what this note is. A
|
||||
* derived tint only means the board should not be a wall of white. Drawing both at
|
||||
* the same weight is what prompted the operator's "the tints look the same as the
|
||||
* chosen colors" — the tint was never meant to shout as loudly as a decision.
|
||||
*/
|
||||
fun noteColorIsChosen(
|
||||
color: String,
|
||||
labelColor: String,
|
||||
known: Set<String>,
|
||||
): Boolean = labelColor.isNotEmpty() || (color.isNotEmpty() && color != "default" && color in known)
|
||||
|
||||
@@ -37,7 +37,8 @@ fun NoteCard(
|
||||
onToggleItem: (Int, Boolean) -> Unit,
|
||||
) {
|
||||
val dark = isSystemInDarkTheme()
|
||||
val tint = noteTintFor(note.id, note.color)
|
||||
val tint = noteTintFor(note)
|
||||
val strong = noteIsStrong(note)
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
@@ -47,7 +48,7 @@ fun NoteCard(
|
||||
// rounded corners instead of a rectangle overhanging them.
|
||||
.clip(RoundedCornerShape(CARD_RADIUS))
|
||||
.clickable(onClickLabel = stringResource(R.string.board_open_note), onClick = onOpen)
|
||||
.background(tint.background(dark))
|
||||
.background(tint.cardBackground(dark, strong))
|
||||
.border(1.dp, tint.border(dark), RoundedCornerShape(CARD_RADIUS))
|
||||
.padding(12.dp),
|
||||
) {
|
||||
|
||||
@@ -63,7 +63,8 @@ fun NoteEditorScreen(
|
||||
onAction: (EditorAction) -> Unit,
|
||||
) {
|
||||
val dark = isSystemInDarkTheme()
|
||||
val tint = noteTintFor(note.id, note.color)
|
||||
val tint = noteTintFor(note)
|
||||
val strong = noteIsStrong(note)
|
||||
|
||||
// Keyed by the SESSION, not by note.id: the editor is reused across notes, so it
|
||||
// needs a key — but a draft's id changes the moment it is first saved, and
|
||||
@@ -155,7 +156,7 @@ fun NoteEditorScreen(
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
shape = RoundedCornerShape(topStart = SHEET_CORNER, topEnd = SHEET_CORNER),
|
||||
color = tint.background(dark),
|
||||
color = tint.cardBackground(dark, strong),
|
||||
// Both content colours are spelled out for the reason the toolbar had to
|
||||
// be: Surface and Scaffold each default theirs to contentColorFor(their
|
||||
// container), which returns Unspecified for anything that is not a
|
||||
@@ -166,7 +167,7 @@ fun NoteEditorScreen(
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
) {
|
||||
Scaffold(
|
||||
containerColor = tint.background(dark),
|
||||
containerColor = tint.cardBackground(dark, strong),
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
topBar = {
|
||||
EditorTopBar(
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.fabledsword.thoughtsync.ui
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.fabledsword.thoughtsync.core.Note
|
||||
|
||||
/**
|
||||
* The note colour palette, matching `frontend/src/notes/colors.ts` VALUE FOR VALUE.
|
||||
@@ -30,9 +31,44 @@ data class NoteTint(
|
||||
val lightChipForeground: Color,
|
||||
val darkChipBackground: Color,
|
||||
val darkChipForeground: Color,
|
||||
/**
|
||||
* False only for `default`, which is the ABSENCE of a colour rather than one of
|
||||
* them. Everything else has two weights (see [cardBackground]); `default` has one,
|
||||
* because there is no such thing as an emphatic lack of colour — and re-alphaing
|
||||
* its opaque neutral fill would make a draft card translucent.
|
||||
*/
|
||||
val tintable: Boolean = true,
|
||||
) {
|
||||
fun background(dark: Boolean): Color = if (dark) darkBackground else lightBackground
|
||||
|
||||
/**
|
||||
* A NOTE card's fill, at one of two weights.
|
||||
*
|
||||
* `strong` means the colour was CHOSEN — by a tag, or (until step 5) by the
|
||||
* picker. Subdued means it was derived from the note's id purely so the board is
|
||||
* not a wall of white. Drawing those at the same weight is what prompted the
|
||||
* operator's "the tints look the same as the chosen colors".
|
||||
*
|
||||
* The two weights move in OPPOSITE directions per theme, because that is where
|
||||
* each has headroom. Light cannot go lighter than `-50` without being white again,
|
||||
* so the gap opens by deepening the chosen end to `-100` — which is exactly
|
||||
* [lightChipBackground], already in this table, so no new hex is transcribed.
|
||||
* Dark CAN go lighter, so the derived end drops to a quarter opacity: closer to
|
||||
* the board, which gives the light body text MORE contrast, not less.
|
||||
*
|
||||
* Borders are untouched. The fill is the signal; moving both muddies the edge.
|
||||
*/
|
||||
fun cardBackground(
|
||||
dark: Boolean,
|
||||
strong: Boolean,
|
||||
): Color =
|
||||
when {
|
||||
!tintable -> background(dark)
|
||||
dark -> darkBackground.copy(alpha = if (strong) STRONG_DARK_ALPHA else SUBDUED_DARK_ALPHA)
|
||||
strong -> lightChipBackground
|
||||
else -> lightBackground
|
||||
}
|
||||
|
||||
fun border(dark: Boolean): Color = if (dark) darkBorder else lightBorder
|
||||
|
||||
fun chipBackground(dark: Boolean): Color = if (dark) darkChipBackground else lightChipBackground
|
||||
@@ -40,6 +76,12 @@ data class NoteTint(
|
||||
fun chipForeground(dark: Boolean): Color = if (dark) darkChipForeground else lightChipForeground
|
||||
}
|
||||
|
||||
// The two dark-theme weights, as fractions. Mirror `dark:bg-{hue}-950/25` and `/70`
|
||||
// in colors.ts — these are the only two numbers that have to agree by hand, since the
|
||||
// light weights are both already columns of the table below.
|
||||
private const val SUBDUED_DARK_ALPHA = 0.25f
|
||||
private const val STRONG_DARK_ALPHA = 0.70f
|
||||
|
||||
/** Keyed by the core's colour vocabulary. Order matches the web's picker. */
|
||||
val NOTE_TINTS: Map<String, NoteTint> =
|
||||
mapOf(
|
||||
@@ -54,6 +96,7 @@ val NOTE_TINTS: Map<String, NoteTint> =
|
||||
lightChipForeground = Color(0xFF525252),
|
||||
darkChipBackground = Color(0x1AFFFFFF),
|
||||
darkChipForeground = Color(0xFFD4D4D4),
|
||||
tintable = false,
|
||||
),
|
||||
"red" to
|
||||
NoteTint(
|
||||
@@ -185,10 +228,31 @@ fun noteTint(key: String): NoteTint = NOTE_TINTS[key] ?: NOTE_TINTS.getValue("de
|
||||
*/
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
fun noteTintFor(
|
||||
id: String,
|
||||
color: String,
|
||||
): NoteTint = noteTint(resolvedNoteColor(id, color, NOTE_TINTS.keys))
|
||||
fun noteTintFor(note: Note): NoteTint =
|
||||
noteTint(resolvedNoteColor(note.id, note.color, firstLabelColor(note), NOTE_TINTS.keys))
|
||||
|
||||
/**
|
||||
* Whether this note's card is drawn at the CHOSEN weight — see [NoteTint.cardBackground].
|
||||
*
|
||||
* Not `@Composable`: the card needs it alongside `isSystemInDarkTheme()`, and keeping
|
||||
* it an ordinary function means it can be read anywhere the note is.
|
||||
*/
|
||||
fun noteIsStrong(note: Note): Boolean =
|
||||
noteColorIsChosen(note.color, firstLabelColor(note), NOTE_TINTS.keys)
|
||||
|
||||
/**
|
||||
* The colour of the note's FIRST label, already resolved, or "" when it has none.
|
||||
*
|
||||
* First rather than any other: it is the one the person controls by typing, where
|
||||
* alphabetical or most-used would move a note's colour when an unrelated tag was
|
||||
* added somewhere else. Manual labels count the same as `#tags` — someone looking at
|
||||
* a chip cannot tell which kind they made, and two identically-tagged notes in
|
||||
* different colours for an invisible reason is worse than the rule being loose.
|
||||
*/
|
||||
private fun firstLabelColor(note: Note): String =
|
||||
note.labels.firstOrNull()
|
||||
?.let { resolvedLabelColor(it.name, it.color, NOTE_TINTS.keys) }
|
||||
?: ""
|
||||
|
||||
/**
|
||||
* The tint for a LABEL, derived from its name when nobody has picked one.
|
||||
|
||||
@@ -64,14 +64,14 @@ class DerivedTintTest {
|
||||
@Test
|
||||
fun `an explicitly picked colour still wins`() {
|
||||
val known = DERIVED_TINT_KEYS.toSet() + "default"
|
||||
assertEquals("teal", resolvedNoteColor("any-id", "teal", known))
|
||||
assertEquals("teal", resolvedNoteColor("any-id", "teal", "", known))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an unknown colour key falls back to the derived tint`() {
|
||||
val known = DERIVED_TINT_KEYS.toSet() + "default"
|
||||
val id = "00000000-0000-0000-0000-000000000000"
|
||||
assertEquals("purple", resolvedNoteColor(id, "chartreuse", known))
|
||||
assertEquals("purple", resolvedNoteColor(id, "chartreuse", "", known))
|
||||
}
|
||||
|
||||
/** `default` is not a choice, it is the absence of one — so a note stored as
|
||||
@@ -81,16 +81,52 @@ class DerivedTintTest {
|
||||
fun `a default colour is treated as no colour`() {
|
||||
val known = DERIVED_TINT_KEYS.toSet() + "default"
|
||||
val id = "11111111-1111-1111-1111-111111111111"
|
||||
assertEquals("blue", resolvedNoteColor(id, "default", known))
|
||||
assertNotEquals("default", resolvedNoteColor(id, "default", known))
|
||||
assertEquals("blue", resolvedNoteColor(id, "default", "", known))
|
||||
assertNotEquals("default", resolvedNoteColor(id, "default", "", known))
|
||||
}
|
||||
|
||||
/** A draft has no id yet. It must not be hashed — see the comment in DerivedTint. */
|
||||
@Test
|
||||
fun `a draft stays default until it has an id`() {
|
||||
val known = DERIVED_TINT_KEYS.toSet() + "default"
|
||||
assertEquals("default", resolvedNoteColor("", "", known))
|
||||
assertEquals("default", resolvedNoteColor("", "default", known))
|
||||
assertEquals("default", resolvedNoteColor("", "", "", known))
|
||||
assertEquals("default", resolvedNoteColor("", "default", "", known))
|
||||
}
|
||||
|
||||
/** The point of the whole milestone: notes sharing a tag share a colour, however
|
||||
* different their ids. Four `#todo` notes in four colours is what started this. */
|
||||
@Test
|
||||
fun `notes sharing a tag share a colour whatever their ids`() {
|
||||
val known = DERIVED_TINT_KEYS.toSet() + "default"
|
||||
val todo = resolvedLabelColor("todo", "default", known)
|
||||
val a = resolvedNoteColor("11111111-1111-1111-1111-111111111111", "default", todo, known)
|
||||
val b = resolvedNoteColor("6ba7b810-9dad-11d1-80b4-00c04fd430c8", "default", todo, known)
|
||||
assertEquals(todo, a)
|
||||
assertEquals(a, b)
|
||||
// ...and it is NOT what either id would have derived on its own.
|
||||
assertNotEquals(derivedTint("11111111-1111-1111-1111-111111111111"), a)
|
||||
}
|
||||
|
||||
/** Until step 5 removes the picker, a hand-picked colour outranks the tag. */
|
||||
@Test
|
||||
fun `an explicit note colour still beats its tag`() {
|
||||
val known = DERIVED_TINT_KEYS.toSet() + "default"
|
||||
val todo = resolvedLabelColor("todo", "default", known)
|
||||
assertNotEquals("teal", todo)
|
||||
assertEquals("teal", resolvedNoteColor("any-id", "teal", todo, known))
|
||||
}
|
||||
|
||||
/** Strength is not a second decision — it IS whether the colour was chosen. */
|
||||
@Test
|
||||
fun `only a chosen colour is drawn strongly`() {
|
||||
val known = DERIVED_TINT_KEYS.toSet() + "default"
|
||||
val todo = resolvedLabelColor("todo", "default", known)
|
||||
assertEquals(true, noteColorIsChosen("default", todo, known))
|
||||
assertEquals(true, noteColorIsChosen("teal", "", known))
|
||||
assertEquals(false, noteColorIsChosen("default", "", known))
|
||||
assertEquals(false, noteColorIsChosen("", "", known))
|
||||
// An unrecognised key is not a choice — it is data we could not read.
|
||||
assertEquals(false, noteColorIsChosen("chartreuse", "", known))
|
||||
}
|
||||
|
||||
/** A tag with no colour of its own derives one from its NAME, which is what makes
|
||||
|
||||
@@ -3,12 +3,11 @@ import { computed, onBeforeUnmount, ref, watch } from "vue";
|
||||
import { useNotesStore } from "../stores/notes";
|
||||
import {
|
||||
LABEL_CHIP_CLASSES,
|
||||
NOTE_CARD_CLASSES,
|
||||
NOTE_COLOR_KEYS,
|
||||
NOTE_COLOR_LABELS,
|
||||
NOTE_SWATCH_CLASSES,
|
||||
noteCardClasses,
|
||||
resolveLabelColor,
|
||||
resolveNoteColor,
|
||||
type NoteColor,
|
||||
} from "../notes/colors";
|
||||
import type { Note } from "../stores/notes";
|
||||
@@ -191,12 +190,6 @@ async function snoozeReminder(minutes: number): Promise<void> {
|
||||
emit("reminder-changed");
|
||||
}
|
||||
|
||||
// Takes the note, not its colour: a note with no colour of its own gets one
|
||||
// derived from its id, so the card can no longer be painted from a single field.
|
||||
function cardClass(note: Note): string {
|
||||
return NOTE_CARD_CLASSES[resolveNoteColor(note)] ?? NOTE_CARD_CLASSES.default;
|
||||
}
|
||||
|
||||
// Takes the label, not its colour: a tag nobody has coloured derives one from its
|
||||
// name, so chips carry the tag's identity rather than all being the same grey.
|
||||
function labelChip(label: { name: string; color: string }): string {
|
||||
@@ -231,7 +224,7 @@ onBeforeUnmount(() => document.removeEventListener("mousedown", onDocMousedown))
|
||||
ref="root"
|
||||
class="group relative mb-4 break-inside-avoid rounded-xl border p-3 shadow-sm transition hover:shadow-md"
|
||||
:class="[
|
||||
cardClass(note),
|
||||
noteCardClasses(note),
|
||||
dragging ? 'opacity-40' : '',
|
||||
dragOver
|
||||
? 'scale-[1.02] shadow-lg ring-2 ring-brand ring-offset-2 ring-offset-white dark:ring-offset-neutral-950'
|
||||
|
||||
@@ -19,15 +19,15 @@ export type NoteColor = (typeof NOTE_COLOR_KEYS)[number];
|
||||
|
||||
export const NOTE_CARD_CLASSES: Record<NoteColor, string> = {
|
||||
default: "bg-white border-neutral-200 dark:bg-neutral-900 dark:border-neutral-700",
|
||||
red: "bg-red-50 border-red-200 dark:bg-red-950/40 dark:border-red-900",
|
||||
orange: "bg-orange-50 border-orange-200 dark:bg-orange-950/40 dark:border-orange-900",
|
||||
yellow: "bg-amber-50 border-amber-200 dark:bg-amber-950/40 dark:border-amber-900",
|
||||
green: "bg-green-50 border-green-200 dark:bg-green-950/40 dark:border-green-900",
|
||||
teal: "bg-teal-50 border-teal-200 dark:bg-teal-950/40 dark:border-teal-900",
|
||||
blue: "bg-blue-50 border-blue-200 dark:bg-blue-950/40 dark:border-blue-900",
|
||||
purple: "bg-purple-50 border-purple-200 dark:bg-purple-950/40 dark:border-purple-900",
|
||||
pink: "bg-pink-50 border-pink-200 dark:bg-pink-950/40 dark:border-pink-900",
|
||||
gray: "bg-neutral-100 border-neutral-300 dark:bg-neutral-800 dark:border-neutral-700",
|
||||
red: "bg-red-50 border-red-200 dark:bg-red-950/25 dark:border-red-900",
|
||||
orange: "bg-orange-50 border-orange-200 dark:bg-orange-950/25 dark:border-orange-900",
|
||||
yellow: "bg-amber-50 border-amber-200 dark:bg-amber-950/25 dark:border-amber-900",
|
||||
green: "bg-green-50 border-green-200 dark:bg-green-950/25 dark:border-green-900",
|
||||
teal: "bg-teal-50 border-teal-200 dark:bg-teal-950/25 dark:border-teal-900",
|
||||
blue: "bg-blue-50 border-blue-200 dark:bg-blue-950/25 dark:border-blue-900",
|
||||
purple: "bg-purple-50 border-purple-200 dark:bg-purple-950/25 dark:border-purple-900",
|
||||
pink: "bg-pink-50 border-pink-200 dark:bg-pink-950/25 dark:border-pink-900",
|
||||
gray: "bg-neutral-100 border-neutral-300 dark:bg-neutral-800/25 dark:border-neutral-700",
|
||||
};
|
||||
|
||||
export const NOTE_SWATCH_CLASSES: Record<NoteColor, string> = {
|
||||
@@ -143,23 +143,6 @@ export function derivedTint(id: string): NoteColor {
|
||||
return DERIVED_TINT_KEYS[tintHash(id) % DERIVED_TINT_KEYS.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* The colour to actually paint a note.
|
||||
*
|
||||
* An explicitly-picked colour still wins — the picker is on its way out
|
||||
* (milestone 309 step 5) but it has not gone yet, and a note the operator
|
||||
* coloured by hand changing under them would read as data loss.
|
||||
*/
|
||||
export function resolveNoteColor(note: { id: string; color?: string | null }): NoteColor {
|
||||
const picked = note.color as NoteColor | undefined | null;
|
||||
if (picked && picked !== "default" && picked in NOTE_CARD_CLASSES) return picked;
|
||||
// No id yet means an unsaved draft: nothing to derive from. Staying white until the
|
||||
// note exists costs one colour change at save time; hashing the empty string would
|
||||
// give every draft the same tint and then change it anyway.
|
||||
if (!note.id) return "default";
|
||||
return derivedTint(note.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The colour to paint a LABEL — its chip, and (step 3) every note carrying it.
|
||||
*
|
||||
@@ -208,3 +191,73 @@ export function resolveLabelColor(label: { name: string; color?: string | null }
|
||||
// Note `work`/`ideas` and `home`/`reading` collide. Nine keys makes that unavoidable
|
||||
// and it is not a bug: colour hints that two notes are related, it never claims they
|
||||
// carry the same tag. The chip's text is what says which tag it is.
|
||||
|
||||
// The FULL-strength ramp: what a note wears when its colour was CHOSEN — by a tag, or
|
||||
// (until step 5) by the picker. One Tailwind step deeper in light mode, and a much
|
||||
// heavier fill in dark.
|
||||
//
|
||||
// The two ramps move in opposite directions per theme, because that is where each has
|
||||
// headroom. Light mode cannot go lighter than `-50` without being white again, so the
|
||||
// gap is opened by deepening the tagged end to `-100`. Dark mode CAN go lighter, so
|
||||
// the untagged end drops to `/25` — closer to the board, which also gives the light
|
||||
// body text MORE contrast rather than less.
|
||||
//
|
||||
// Borders stay put. The fill is the signal; moving both just muddies the edge.
|
||||
export const NOTE_CARD_CLASSES_STRONG: Record<NoteColor, string> = {
|
||||
default: "bg-white border-neutral-200 dark:bg-neutral-900 dark:border-neutral-700",
|
||||
red: "bg-red-100 border-red-200 dark:bg-red-950/70 dark:border-red-900",
|
||||
orange: "bg-orange-100 border-orange-200 dark:bg-orange-950/70 dark:border-orange-900",
|
||||
yellow: "bg-amber-100 border-amber-200 dark:bg-amber-950/70 dark:border-amber-900",
|
||||
green: "bg-green-100 border-green-200 dark:bg-green-950/70 dark:border-green-900",
|
||||
teal: "bg-teal-100 border-teal-200 dark:bg-teal-950/70 dark:border-teal-900",
|
||||
blue: "bg-blue-100 border-blue-200 dark:bg-blue-950/70 dark:border-blue-900",
|
||||
purple: "bg-purple-100 border-purple-200 dark:bg-purple-950/70 dark:border-purple-900",
|
||||
pink: "bg-pink-100 border-pink-200 dark:bg-pink-950/70 dark:border-pink-900",
|
||||
gray: "bg-neutral-200 border-neutral-300 dark:bg-neutral-800/70 dark:border-neutral-700",
|
||||
};
|
||||
|
||||
/**
|
||||
* The colour a note wears AND how strongly, in one answer.
|
||||
*
|
||||
* `strong` is not a second decision — it IS whether the colour was chosen. A tag (or,
|
||||
* until step 5, the picker) means somebody said what this note is; a derived tint only
|
||||
* means the board should not be a wall of white. Rendering those two at the same
|
||||
* weight is what made the operator ask "the tints look the same as the chosen colors".
|
||||
*
|
||||
* Resolution order, and why: an explicit pick beats a tag because it is the more
|
||||
* specific statement and the picker still exists. The FIRST label wins among tags —
|
||||
* it is the one the person controls by typing, where alphabetical or most-used would
|
||||
* move a note's colour when an unrelated tag was added somewhere else.
|
||||
*
|
||||
* Manual labels count the same as `#tags`. Someone looking at a chip cannot tell which
|
||||
* kind they made, and two identically-tagged notes in different colours for an
|
||||
* invisible reason is worse than the rule being slightly loose.
|
||||
*/
|
||||
export function resolveNoteTint(note: {
|
||||
id: string;
|
||||
color?: string | null;
|
||||
labels?: { name: string; color: string }[];
|
||||
}): { color: NoteColor; strong: boolean } {
|
||||
const picked = note.color as NoteColor | undefined | null;
|
||||
if (picked && picked !== "default" && picked in NOTE_CARD_CLASSES) {
|
||||
return { color: picked, strong: true };
|
||||
}
|
||||
const first = note.labels?.[0];
|
||||
if (first) return { color: resolveLabelColor(first), strong: true };
|
||||
// No id yet means an unsaved draft: nothing to derive from. Staying white until the
|
||||
// note exists costs one colour change at save time; hashing the empty string would
|
||||
// give every draft the same tint and then change it anyway.
|
||||
if (!note.id) return { color: "default", strong: false };
|
||||
return { color: derivedTint(note.id), strong: false };
|
||||
}
|
||||
|
||||
/** The card classes for a note — both ramps behind one call. */
|
||||
export function noteCardClasses(note: {
|
||||
id: string;
|
||||
color?: string | null;
|
||||
labels?: { name: string; color: string }[];
|
||||
}): string {
|
||||
const { color, strong } = resolveNoteTint(note);
|
||||
const ramp = strong ? NOTE_CARD_CLASSES_STRONG : NOTE_CARD_CLASSES;
|
||||
return ramp[color] ?? NOTE_CARD_CLASSES.default;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user