Merge pull request 'Clan-app: add dependencies; floating-ui; eslint-query' (#1808) from hsjobeki/clan-core:hsjobeki-main into main

This commit is contained in:
clan-bot
2024-07-25 12:29:16 +00:00
6 changed files with 366 additions and 162 deletions

View File

@@ -1,9 +1,11 @@
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import tailwind from "eslint-plugin-tailwindcss";
import pluginQuery from "@tanstack/eslint-plugin-query";
export default tseslint.config(
eslint.configs.recommended,
...pluginQuery.configs["flat/recommended"],
...tseslint.configs.strict,
...tseslint.configs.stylistic,
...tailwind.configs["flat/recommended"],

File diff suppressed because it is too large Load Diff

View File

@@ -38,8 +38,10 @@
"vitest": "^1.6.0"
},
"dependencies": {
"@floating-ui/dom": "^1.6.8",
"@modular-forms/solid": "^0.21.0",
"@solid-primitives/storage": "^3.7.1",
"@tanstack/eslint-plugin-query": "^5.51.12",
"@tanstack/solid-query": "^5.51.2",
"material-icons": "^1.13.12",
"nanoid": "^5.0.7",

View 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,
};
}

View File

@@ -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<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 (
<div class="stat">
<div class="stat-figure text-primary">
@@ -72,23 +106,43 @@ const ClanDetails = (props: ClanDetailsProps) => {
{activeURI() === clan_dir ? "active" : "select"}
</button>
<button
popovertarget={`clan-delete-popover-${clan_dir}`}
popovertargetaction="toggle"
ref={setReference}
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
</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 class="stat-title">Clan URI</div>

View File

@@ -16,7 +16,7 @@
npmDeps = pkgs.fetchNpmDeps {
src = ./app;
hash = "sha256-/PFSBAIodZjInElYoNsDQUV4isxmcvL3YM1hzAmdDWA=";
hash = "sha256-n9IXcfCpydykoYD+P/YNtNIwrvgJTZND0kg7oXBfmJ0=";
};
# The prepack script runs the build script, which we'd rather do in the build phase.
npmPackFlags = [ "--ignore-scripts" ];