Clan-app: add popover - remove clan confirm
This commit is contained in:
128
pkgs/webview-ui/app/src/floating/index.tsx
Normal file
128
pkgs/webview-ui/app/src/floating/index.tsx
Normal file
@@ -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<ComputePositionConfig> {
|
||||||
|
whileElementsMounted?: (
|
||||||
|
reference: R,
|
||||||
|
floating: F,
|
||||||
|
update: () => void
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
|
||||||
|
) => void | (() => void);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseFloatingState extends Omit<ComputePositionReturn, "x" | "y"> {
|
||||||
|
x?: number | null;
|
||||||
|
y?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseFloatingResult extends UseFloatingState {
|
||||||
|
update(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFloating<R extends ReferenceElement, F extends HTMLElement>(
|
||||||
|
reference: () => R | undefined | null,
|
||||||
|
floating: () => F | undefined | null,
|
||||||
|
options?: UseFloatingOptions<R, F>
|
||||||
|
): UseFloatingResult {
|
||||||
|
const placement = () => options?.placement ?? "bottom";
|
||||||
|
const strategy = () => options?.strategy ?? "absolute";
|
||||||
|
|
||||||
|
const [data, setData] = createSignal<UseFloatingState>({
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -12,8 +12,18 @@ import {
|
|||||||
setRoute,
|
setRoute,
|
||||||
clanList,
|
clanList,
|
||||||
} from "@/src/App";
|
} from "@/src/App";
|
||||||
import { For, Show } from "solid-js";
|
import { createEffect, createSignal, For, Show } from "solid-js";
|
||||||
import { createQuery } from "@tanstack/solid-query";
|
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 () => {
|
export const registerClan = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -54,6 +64,30 @@ const ClanDetails = (props: ClanDetailsProps) => {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const [reference, setReference] = createSignal<HTMLElement>();
|
||||||
|
const [floating, setFloating] = createSignal<HTMLElement>();
|
||||||
|
const [arrowEl, setArrowEl] = createSignal<HTMLElement>();
|
||||||
|
|
||||||
|
// `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 (
|
return (
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-figure text-primary">
|
<div class="stat-figure text-primary">
|
||||||
@@ -72,23 +106,43 @@ const ClanDetails = (props: ClanDetailsProps) => {
|
|||||||
{activeURI() === clan_dir ? "active" : "select"}
|
{activeURI() === clan_dir ? "active" : "select"}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
popovertarget={`clan-delete-popover-${clan_dir}`}
|
||||||
|
popovertargetaction="toggle"
|
||||||
|
ref={setReference}
|
||||||
class="btn btn-ghost btn-outline join-item btn-sm"
|
class="btn btn-ghost btn-outline join-item btn-sm"
|
||||||
onClick={() => {
|
|
||||||
setClanList((s) =>
|
|
||||||
s.filter((v, idx) => {
|
|
||||||
if (v == clan_dir) {
|
|
||||||
setActiveURI(
|
|
||||||
clanList()[idx - 1] || clanList()[idx + 1] || null
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
|
<div
|
||||||
|
popover="auto"
|
||||||
|
id={`clan-delete-popover-${clan_dir}`}
|
||||||
|
ref={setFloating}
|
||||||
|
style={{
|
||||||
|
position: position.strategy,
|
||||||
|
top: `${position.y ?? 0}px`,
|
||||||
|
left: `${position.x ?? 0}px`,
|
||||||
|
}}
|
||||||
|
class="bg-transparent"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn btn-warning btn-sm"
|
||||||
|
onClick={() => {
|
||||||
|
setClanList((s) =>
|
||||||
|
s.filter((v, idx) => {
|
||||||
|
if (v == clan_dir) {
|
||||||
|
setActiveURI(
|
||||||
|
clanList()[idx - 1] || clanList()[idx + 1] || null
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Remove from App
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-title">Clan URI</div>
|
<div class="stat-title">Clan URI</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user