From b48135ca51df8dea0256d664957ef6e6d0cc197a Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Thu, 25 Jul 2024 13:10:06 +0200 Subject: [PATCH] Clan-app: add popover - remove clan confirm --- pkgs/webview-ui/app/src/floating/index.tsx | 128 ++++++++++++++++++ .../app/src/routes/settings/index.tsx | 82 +++++++++-- 2 files changed, 196 insertions(+), 14 deletions(-) create mode 100644 pkgs/webview-ui/app/src/floating/index.tsx diff --git a/pkgs/webview-ui/app/src/floating/index.tsx b/pkgs/webview-ui/app/src/floating/index.tsx new file mode 100644 index 000000000..96267c82f --- /dev/null +++ b/pkgs/webview-ui/app/src/floating/index.tsx @@ -0,0 +1,128 @@ +import { createEffect, createMemo, createSignal, onCleanup } from "solid-js"; +import type { + ComputePositionConfig, + ComputePositionReturn, + ReferenceElement, +} from "@floating-ui/dom"; +import { computePosition } from "@floating-ui/dom"; + +export interface UseFloatingOptions< + R extends ReferenceElement, + F extends HTMLElement, +> extends Partial { + whileElementsMounted?: ( + reference: R, + floating: F, + update: () => void + // eslint-disable-next-line @typescript-eslint/no-invalid-void-type + ) => void | (() => void); +} + +interface UseFloatingState extends Omit { + x?: number | null; + y?: number | null; +} + +export interface UseFloatingResult extends UseFloatingState { + update(): void; +} + +export function useFloating( + reference: () => R | undefined | null, + floating: () => F | undefined | null, + options?: UseFloatingOptions +): UseFloatingResult { + const placement = () => options?.placement ?? "bottom"; + const strategy = () => options?.strategy ?? "absolute"; + + const [data, setData] = createSignal({ + x: null, + y: null, + placement: placement(), + strategy: strategy(), + middlewareData: {}, + }); + + const [error, setError] = createSignal<{ value: unknown } | undefined>(); + + createEffect(() => { + const currentError = error(); + if (currentError) { + throw currentError.value; + } + }); + + const version = createMemo(() => { + reference(); + floating(); + return {}; + }); + + function update() { + const currentReference = reference(); + const currentFloating = floating(); + + if (currentReference && currentFloating) { + const capturedVersion = version(); + computePosition(currentReference, currentFloating, { + middleware: options?.middleware, + placement: placement(), + strategy: strategy(), + }).then( + (currentData) => { + // Check if it's still valid + if (capturedVersion === version()) { + setData(currentData); + } + }, + (err) => { + setError(err); + } + ); + } + } + + createEffect(() => { + const currentReference = reference(); + const currentFloating = floating(); + + options?.middleware; + placement(); + strategy(); + + if (currentReference && currentFloating) { + if (options?.whileElementsMounted) { + const cleanup = options.whileElementsMounted( + currentReference, + currentFloating, + update + ); + + if (cleanup) { + onCleanup(cleanup); + } + } else { + update(); + } + } + }); + + return { + get x() { + return data().x; + }, + get y() { + return data().y; + }, + get placement() { + return data().placement; + }, + get strategy() { + return data().strategy; + }, + get middlewareData() { + return data().middlewareData; + }, + update, + }; +} diff --git a/pkgs/webview-ui/app/src/routes/settings/index.tsx b/pkgs/webview-ui/app/src/routes/settings/index.tsx index 29eb2846b..1c83c41ab 100644 --- a/pkgs/webview-ui/app/src/routes/settings/index.tsx +++ b/pkgs/webview-ui/app/src/routes/settings/index.tsx @@ -12,8 +12,18 @@ import { setRoute, clanList, } from "@/src/App"; -import { For, Show } from "solid-js"; +import { createEffect, createSignal, For, Show } from "solid-js"; import { createQuery } from "@tanstack/solid-query"; +import { useFloating } from "@/src/floating"; +import { + arrow, + autoUpdate, + flip, + hide, + offset, + shift, + size, +} from "@floating-ui/dom"; export const registerClan = async () => { try { @@ -54,6 +64,30 @@ const ClanDetails = (props: ClanDetailsProps) => { }, })); + const [reference, setReference] = createSignal(); + const [floating, setFloating] = createSignal(); + const [arrowEl, setArrowEl] = createSignal(); + + // `position` is a reactive object. + const position = useFloating(reference, floating, { + placement: "top", + + // pass options. Ensure the cleanup function is returned. + whileElementsMounted: (reference, floating, update) => + autoUpdate(reference, floating, update, { + animationFrame: true, + }), + middleware: [ + offset(5), + shift(), + flip(), + + hide({ + strategy: "referenceHidden", + }), + ], + }); + return (
@@ -72,23 +106,43 @@ const ClanDetails = (props: ClanDetailsProps) => { {activeURI() === clan_dir ? "active" : "select"} +
+ +
Clan URI