Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/src/common/generic/lib/create-url/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ type QueryValue = boolean | number | string;

type AnyUrlInput = {
fragment?: null | string;
query?: null | { [key: string]: QueryValue | QueryValue[] };
query?: null | { [key: string]: QueryValue | readonly QueryValue[] };
};

type AbsoluteUrlInput = AnyUrlInput & {
Expand Down
2 changes: 2 additions & 0 deletions src/src/isomorphic/generic/components/click/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Click } from "./main";
export type { BaseClickInput, ClickInput } from "./types";
24 changes: 24 additions & 0 deletions src/src/isomorphic/generic/components/click/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"use client";

import { createPolymorphicComponent } from "@mantine/core";
import { useMergedRef } from "@mantine/hooks";
import { useEffect, useRef } from "react";

import type { BaseClickInput, ClickInput } from "./types";

export const Click = createPolymorphicComponent<"a", BaseClickInput>(
function Click({ component: Component = "a", ref, ...input }: ClickInput) {
const clicked = useRef(false);
const innerRef = useRef<HTMLElement>(null);
const mergedRef = useMergedRef(ref, innerRef);

useEffect(() => {
if (clicked.current || !innerRef.current) return;

innerRef.current.click();
clicked.current = true;
}, []);

return <Component ref={mergedRef} {...input} />;
},
);
9 changes: 9 additions & 0 deletions src/src/isomorphic/generic/components/click/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { PolymorphicComponentProps } from "@mantine/core";
import type { ElementType } from "react";

export type BaseClickInput = object;

export type ClickInput<C extends ElementType = "a"> = PolymorphicComponentProps<
C,
BaseClickInput
>;
2 changes: 2 additions & 0 deletions src/src/isomorphic/generic/components/client-link/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { ClientLink } from "./main";
export type { ClientLinkInput } from "./types";
9 changes: 9 additions & 0 deletions src/src/isomorphic/generic/components/client-link/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"use client";

import Link from "next/link";

import type { ClientLinkInput } from "./types";

export function ClientLink({ ...input }: ClientLinkInput) {
return <Link {...input} />;
}
4 changes: 4 additions & 0 deletions src/src/isomorphic/generic/components/client-link/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type Link from "next/link";
import type { ComponentProps } from "react";

export type ClientLinkInput = ComponentProps<typeof Link>;
8 changes: 8 additions & 0 deletions src/src/isomorphic/generic/components/history-link/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export { HistoryLink } from "./main";
export type {
HistoryLinkAction,
HistoryLinkActions,
HistoryLinkInput,
HistoryLinkPopAction,
HistoryLinkPushAction,
} from "./types";
30 changes: 30 additions & 0 deletions src/src/isomorphic/generic/components/history-link/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"use client";

import { range } from "es-toolkit/math";
import { useCallback } from "react";
import { useDeepCompareMemo } from "use-deep-compare";

import type { HistoryLinkInput } from "./types";

import { useGlobalState } from "../../../state/hooks/use-global-state";
import { ClientLink } from "../client-link";

export function HistoryLink({ actions, ...input }: HistoryLinkInput) {
const { state } = useGlobalState();

const cachedActions = useDeepCompareMemo(() => actions, [actions]);

const handleNavigate = useCallback(() => {
const entries = state.current.history.entries;

for (const action of cachedActions) {
if (action.type === "pop") {
range(action.count ?? 1).forEach(() => entries.pop());
} else if (action.type === "push") {
entries.push(...action.entries);
}
}
}, [cachedActions, state.current.history.entries]);

return <ClientLink {...input} onNavigate={handleNavigate} />;
}
20 changes: 20 additions & 0 deletions src/src/isomorphic/generic/components/history-link/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { HistoryEntry } from "../../../state/types";
import type { ClientLinkInput } from "../client-link";

export type HistoryLinkPopAction = {
count?: number;
type: "pop";
};

export type HistoryLinkPushAction = {
entries: HistoryEntry[];
type: "push";
};

export type HistoryLinkAction = HistoryLinkPopAction | HistoryLinkPushAction;

export type HistoryLinkActions = HistoryLinkAction[];

export type HistoryLinkInput = ClientLinkInput & {
actions: HistoryLinkActions;
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ export function HistorySynchronizer({}: HistorySynchronizerInput) {
path: pathname,
query: searchParams.entries().reduce(
(acc, [key, value]) => {
acc ??= {};

if (acc[key] === undefined) acc[key] = value;
else if (Array.isArray(acc[key])) acc[key].push(value);
else acc[key] = [acc[key], value];

return acc;
},
{} as { [key: string]: string | string[] },
undefined as undefined | { [key: string]: string | string[] },
),
};

Expand Down
29 changes: 11 additions & 18 deletions src/src/isomorphic/generic/components/route-modal/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,23 @@ import { useCallback } from "react";

import type { RouteModalInput } from "./types";

import { useLocalization } from "../../../localization/hooks/use-localization";
import { createUrl } from "../../../../common/generic/lib/create-url";
import { useHistory } from "../../hooks/use-history";

export function RouteModal({ fallback, title, ...props }: RouteModalInput) {
export function RouteModal({ fallback, force, ...input }: RouteModalInput) {
const router = useRouter();

const { history } = useHistory();
const { localization } = useLocalization();

const handleClose = useCallback(() => {
if (history.entries.length > 1) router.back();
else router.push(fallback);
}, [fallback, history.entries.length, router]);
if (!force && history.entries.length > 1) {
const target = history.entries[history.entries.length - 2]!;
const { url } = createUrl({ path: target.path, query: target.query });
router.push(url);
} else {
router.push(fallback);
}
}, [fallback, force, history.entries, history.entries.length, router]);

return (
<Modal
{...props}
onClose={handleClose}
opened={true}
title={
title === undefined || typeof title === "string"
? title
: localization.localize(title)
}
/>
);
return <Modal {...input} onClose={handleClose} opened={true} />;
}
8 changes: 2 additions & 6 deletions src/src/isomorphic/generic/components/route-modal/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import type { MessageDescriptor } from "@lingui/core";
import type { ModalProps } from "@mantine/core";

export type RouteModalInput = Omit<
ModalProps,
"onClose" | "opened" | "title"
> & {
export type RouteModalInput = Omit<ModalProps, "onClose" | "opened"> & {
fallback: string;
title?: MessageDescriptor | string;
force?: boolean;
};
2 changes: 2 additions & 0 deletions src/src/isomorphic/localization/components/localized/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Localized } from "./main";
export type { LocalizedInput } from "./types";
11 changes: 11 additions & 0 deletions src/src/isomorphic/localization/components/localized/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use client";

import type { LocalizedInput } from "./types";

import { useLocalization } from "../../hooks/use-localization";

export function Localized({ message }: LocalizedInput) {
const { localization } = useLocalization();

return localization.localize(message);
}
5 changes: 5 additions & 0 deletions src/src/isomorphic/localization/components/localized/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { MessageDescriptor } from "@lingui/core";

export type LocalizedInput = {
message: MessageDescriptor;
};
2 changes: 1 addition & 1 deletion src/src/isomorphic/state/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { ReadonlyDeep } from "type-fest";

export type HistoryEntry = {
path: string;
query: { [key: string]: string | string[] };
query?: { [key: string]: string | string[] };
};

export type HistoryState = {
Expand Down
Loading