feat(react/ribbon): port editability select

This commit is contained in:
Elian Doran
2025-08-21 22:19:26 +03:00
parent 1964fb90d5
commit f772f59d7c
7 changed files with 117 additions and 138 deletions

View File

@@ -0,0 +1,30 @@
import Dropdown from "./Dropdown";
import { FormListItem } from "./FormList";
interface FormDropdownList<T> {
values: T[];
keyProperty: keyof T;
titleProperty: keyof T;
descriptionProperty?: keyof T;
currentValue: string;
onChange(newValue: string): void;
}
export default function FormDropdownList<T>({ values, keyProperty, titleProperty, descriptionProperty, currentValue, onChange }: FormDropdownList<T>) {
const currentValueData = values.find(value => value[keyProperty] === currentValue);
return (
<Dropdown text={currentValueData?.[titleProperty] ?? ""}>
{values.map(item => (
<FormListItem
onClick={() => onChange(item[keyProperty] as string)}
checked={currentValue === item[keyProperty]}
description={descriptionProperty && item[descriptionProperty] as string}
selected={currentValue === item[keyProperty]}
>
{item[titleProperty] as string}
</FormListItem>
))}
</Dropdown>
)
}

View File

@@ -0,0 +1,5 @@
.dropdown-item .description {
font-size: small;
color: var(--muted-text-color);
white-space: normal;
}

View File

@@ -2,6 +2,7 @@ import { Dropdown as BootstrapDropdown } from "bootstrap";
import { ComponentChildren } from "preact";
import Icon from "./Icon";
import { useEffect, useMemo, useRef, type CSSProperties } from "preact/compat";
import "./FormList.css";
interface FormListOpts {
children: ComponentChildren;
@@ -76,27 +77,33 @@ interface FormListItemOpts {
active?: boolean;
badges?: FormListBadge[];
disabled?: boolean;
checked?: boolean;
checked?: boolean | null;
selected?: boolean;
onClick?: () => void;
description?: string;
className?: string;
}
export function FormListItem({ children, icon, value, title, active, badges, disabled, checked, onClick }: FormListItemOpts) {
export function FormListItem({ children, icon, value, title, active, badges, disabled, checked, onClick, description, selected }: FormListItemOpts) {
if (checked) {
icon = "bx bx-check";
}
return (
<a
class={`dropdown-item ${active ? "active" : ""} ${disabled ? "disabled" : ""}`}
class={`dropdown-item ${active ? "active" : ""} ${disabled ? "disabled" : ""} ${selected ? "selected" : ""}`}
data-value={value} title={title}
tabIndex={0}
onClick={onClick}
>
<Icon icon={icon} />&nbsp;
{children}
{badges && badges.map(({ className, text }) => (
<span className={`badge ${className ?? ""}`}>{text}</span>
))}
<div>
{children}
{badges && badges.map(({ className, text }) => (
<span className={`badge ${className ?? ""}`}>{text}</span>
))}
{description && <div className="description">{description}</div>}
</div>
</a>
);
}

View File

@@ -314,12 +314,12 @@ export function useNoteProperty<T extends keyof FNote>(note: FNote | null | unde
}
export function useNoteLabel(note: FNote | undefined | null, labelName: string): [string | null | undefined, (newValue: string) => void] {
const [ labelValue, setNoteValue ] = useState<string | null | undefined>(note?.getLabelValue(labelName));
const [ labelValue, setLabelValue ] = useState<string | null | undefined>(note?.getLabelValue(labelName));
useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => {
for (const attr of loadResults.getAttributeRows()) {
if (attr.type === "label" && attr.name === labelName && attributes.isAffecting(attr, note)) {
setNoteValue(attr.value ?? null);
setLabelValue(attr.value ?? null);
}
}
});
@@ -334,4 +334,28 @@ export function useNoteLabel(note: FNote | undefined | null, labelName: string):
labelValue,
setter
] as const;
}
export function useNoteLabelBoolean(note: FNote | undefined | null, labelName: string): [ boolean | null | undefined, (newValue: boolean) => void] {
const [ labelValue, setLabelValue ] = useState<boolean | null | undefined>(note?.hasLabel(labelName));
useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => {
for (const attr of loadResults.getAttributeRows()) {
if (attr.type === "label" && attr.name === labelName && attributes.isAffecting(attr, note)) {
setLabelValue(!attr.isDeleted);
}
}
});
const setter = useCallback((value: boolean) => {
if (note) {
if (value) {
attributes.setLabel(note.noteId, labelName, "");
} else {
attributes.removeOwnedLabelByName(note, labelName);
}
}
}, [note]);
return [ labelValue, setter ] as const;
}