Skip to content

Commit

Permalink
sync nextutils
Browse files Browse the repository at this point in the history
  • Loading branch information
mrbirddev committed Jan 20, 2025
1 parent 0c0504c commit 25765c4
Show file tree
Hide file tree
Showing 17 changed files with 940 additions and 23 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"sass": "^1.70.0",
"superjson": "^2.2.1",
"tiny-invariant": "^1.3.3",
"usehooks-ts": "^3.1.0",
"webfontloader": "^1.6.28",
"zod": "^3.22.4",
"zustand": "^4.5.1"
Expand Down
10 changes: 6 additions & 4 deletions src/@nextutils/_app/i18nLangAltHead.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ import React from "react";
import {AppFC, AppMiddleware} from "./AppMiddleware";
import {getCanonicalUrl, hackAsPath} from "@nextutils/i18n/locales";
import {noIndexPathList, noLangAltList, PRODUCT_DOMAIN, PRODUCT_NAME} from "@nextutils/config";
import { useIsClient } from "usehooks-ts";

const i18nLangAltHead: AppMiddleware = (App) => {
const Augmented: AppFC = (props) => {
const t = useI18n();
const currentLocale = useCurrentLocale();
const router = useRouter();
const canonical = getCanonicalUrl(currentLocale, router.asPath);
const isClient = useIsClient();
const canonical = getCanonicalUrl(currentLocale, router.asPath, isClient);

const noIndex = !!noIndexPathList.find(prefix => hackAsPath(router.asPath).startsWith(prefix));
const noLangAlt = !!noLangAltList.find(prefix => hackAsPath(router.asPath).startsWith(prefix));
const noIndex = !!noIndexPathList.find(prefix => hackAsPath(router.asPath, isClient).startsWith(prefix));
const noLangAlt = !!noLangAltList.find(prefix => hackAsPath(router.asPath, isClient).startsWith(prefix));

const seo = <NextSeo
noindex={noIndex}
Expand All @@ -33,7 +35,7 @@ const i18nLangAltHead: AppMiddleware = (App) => {
...allLocaleStrList.filter(localeStr => localeStr !== currentLocale).map(localeStr => {
return {
hrefLang: localeStr,
href: getCanonicalUrl(localeStr, router.asPath),
href: getCanonicalUrl(localeStr, router.asPath, isClient),
};
}),

Expand Down
17 changes: 9 additions & 8 deletions src/@nextutils/i18n/ChooseLocaleModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import {useScopedI18n} from "@nextutils/locales";
import {useRouter} from "next/router";
import Link from "next/link";
import useUserPreferencesStore from "@nextutils/useUserPreferencesStore";
import ModalDialog from "@nextutils/ui/modal/ModalDialog";
import {ModalTitle} from "@nextutils/ui/modal/commonComponents";
import {locales} from "@nextutils/config";
import ActionSheetWrapper from "@nextutils/ui/actionSheet/ActionSheetWrapper";
import {cx} from "@emotion/css";

const ChooseLocaleModal = (props: {
isOpen: boolean,
Expand All @@ -16,21 +16,22 @@ const ChooseLocaleModal = (props: {
const router = useRouter();

return (
<ModalDialog isOpen={props.isOpen} setOpen={props.setOpen}>
<ModalTitle onCancel={() => props.setOpen(false)}>
{tI18nMsg('Choose language')}
</ModalTitle>
<ActionSheetWrapper
title={tI18nMsg('Choose language')}
onCancel={() => props.setOpen(false)}
isHidden={!props.isOpen}
>
<div className="flex flex-wrap justify-start items-start gap-6">
{locales.map(item => {
return <Link key={item.locale} className="link link-ghost" href={hackAsPath(router.asPath)} locale={item.locale} onClick={() => {
return <Link key={item.locale} className="link link-ghost" href={hackAsPath(router.asPath, true)} locale={item.locale} onClick={() => {
useUserPreferencesStore.getState().setLocale(item.locale);
props.setOpen(false);
}}>
{item.lang}
</Link>
})}
</div>
</ModalDialog>
</ActionSheetWrapper>
);
}

Expand Down
28 changes: 21 additions & 7 deletions src/@nextutils/i18n/locales.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ export const getCanonicalPath = (
}`
}

// Fix this on vercel
// https://github.com/vercel/next.js/issues/72063
export const hackAsPath = (asPath: string) => {
const FAKE_DOMAIN = 'https://fake.com';
export const hackAsPath = (asPath: string, isClient: boolean) => {
// Fix this on vercel
// https://github.com/vercel/next.js/issues/72063
let finalPath = asPath;
for (const l of locales) {
if (asPath.startsWith('/' + l.locale)) {
Expand All @@ -60,21 +61,34 @@ export const hackAsPath = (asPath: string) => {
}
}

const url = new URL(finalPath, FAKE_DOMAIN);

// remove the parameter nxtPslugList
finalPath = finalPath.split('?')[0]!;
if (url.searchParams.has('nxtPslugList')) {
url.searchParams.delete('nxtPslugList');
}

// Fix hydration of hash - remove hash on server
// https://github.com/vercel/next.js/issues/25202
if (!isClient) {
url.hash = '';
}

finalPath = url.toString();

return finalPath;
return finalPath.slice(FAKE_DOMAIN.length);
};

export const getCanonicalUrl = (
locale: string,
asPath: string // use router.asPath
asPath: string, // use router.asPath,
isClient: boolean,
) => {
return `https://${
PRODUCT_DOMAIN
}${
locale === fallbackLocale ? '' : '/' + locale
}${
hackAsPath(asPath) === '/' ? '' : hackAsPath(asPath)
hackAsPath(asPath, isClient) === '/' ? '' : hackAsPath(asPath, isClient)
}`
};
14 changes: 14 additions & 0 deletions src/@nextutils/ui/ClientOnly.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {PropsWithChildren, useEffect, useState} from "react";
import {useIsClient} from "usehooks-ts";

const ClientOnly = (props: PropsWithChildren) => {
const isClient = useIsClient();

if (!isClient) {
return false;
}

return props.children;
};

export default ClientOnly;
10 changes: 10 additions & 0 deletions src/@nextutils/ui/Portal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {createPortal} from "react-dom";
import {PropsWithChildren} from "react";
const Portal = (props: PropsWithChildren<{id: string}>) => {
return createPortal(
props.children,
document.getElementById(props.id)!
);
};

export default Portal;
28 changes: 28 additions & 0 deletions src/@nextutils/ui/actionSheet/ActionSheetBackdrop.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {cx} from "@emotion/css";
import rootStackingContext from "@nextutils/ui/rootStackingContext";
import React from "react";

const ActionSheetBackdrop = (props: {
isOpen: boolean,
onBackdropClick?: () => void,
children?: React.ReactNode;
}) => {
return (<div className={cx(
"fixed inset-0",
rootStackingContext.actionSheetBackdrop,
"flex items-center justify-center",
"transition overflow-hidden",
props.isOpen ? "bg-black/60" : "bg-black/0",
props.isOpen ? "" : "hidden",
)} onClick={(e) => {
// only when clicking on the backdrop itself
// https://stackoverflow.com/a/47406614/1922857
if (e.target === e.currentTarget) {
props.onBackdropClick?.();
}
}}>
{props.children}
</div>);
};

export default ActionSheetBackdrop;
121 changes: 121 additions & 0 deletions src/@nextutils/ui/actionSheet/ActionSheetMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import {Menu, MenuDivider, MenuItem, SubMenu} from "@szhsin/react-menu";
import React, {ReactNode} from "react";
import useIsMobileWidth from "@nextutils/ui/useIsMobileWidth";
import useActionSheetStore from "@nextutils/ui/actionSheet/useActionSheetStore";
import ActionSheetWrapper from "@nextutils/ui/actionSheet/ActionSheetWrapper";
import MobileStackRow from "@slidde/editor2/MobileStackRow";

export type ActionSheetMenuItem = {
custom: ReactNode;
} | {
label?: ReactNode;
icon?: ReactNode;
href?: string;
onClick?: () => void;

items?: ActionSheetMenuItem[];
} | 'divider';

const ActionSheetMenuItem = (props: {item: ActionSheetMenuItem, depth: number}) => {
const {item, depth} = props;
if (item === 'divider') {
return <div className="my-2 h-[1px] bg-slate-300" />;
}
if ('custom' in item) {
return <div className="my-2">{item.custom}</div>;
}

const {items} = item;
if (items) {
return <MobileStackRow
{...item}
onClick={() => {
useActionSheetStore.getState().push(
<ActionSheetWrapper>
{items.map((item, index) => {
return <ActionSheetMenuItem key={index} item={item} depth={depth + 1} />;
})}
</ActionSheetWrapper>
)
}}
/>;
}

return <MobileStackRow
{...item}
onClick={() => {
// leaf level, do close
// do it before onClick, since the onClick may trigger a new action sheet
useActionSheetStore.getState().pop(depth);

item.onClick?.();
}}
/>;
}

const ActionSheetMenu = (props: {
button: ReactNode,
buttonClassName?: string,
items: ActionSheetMenuItem[],
}) => {
const isMobileWidth = useIsMobileWidth();

if (isMobileWidth) {
return <div className={props.buttonClassName} onClick={() => {
useActionSheetStore.getState().push(
<ActionSheetWrapper>
{props.items.map((item, index) => {
return <ActionSheetMenuItem key={index} item={item} depth={1} />;
})}
</ActionSheetWrapper>
);
}}>
{props.button}
</div>
}

return <Menu menuButton={(modifiers) => <div className={props.buttonClassName}>{props.button}</div>}>
{props.items.map((item, index) => {
if (item === 'divider') {
return <MenuDivider key={index} />;
}
if ('custom' in item) {
return <div key={index} className="max-w-[20rem] px-2">{item.custom}</div>;
}

if (item.items) {
return <SubMenu key={index} label={<>{item.icon} {item.label}</>}>
{item.items.map((item, index) => {
if (item === 'divider') {
return <MenuDivider key={index} />;
}

if ('custom' in item) {
return <div key={index} className="max-w-[20rem] px-2">{item.custom}</div>;
}

return <MenuItem
key={index}
href={item.href}
onClick={item.onClick}
>
{item.icon}
{item.label}
</MenuItem>;
})}
</SubMenu>
}

return <MenuItem
key={index}
href={item.href}
onClick={item.onClick}
>
{item.icon}
{item.label}
</MenuItem>;
})}
</Menu>;
};

export default ActionSheetMenu;
14 changes: 14 additions & 0 deletions src/@nextutils/ui/actionSheet/ActionSheetRenderStack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {cx} from "@emotion/css";
import React, {ReactNode} from "react";

const ActionSheetRenderStack = (props: {stack: ReactNode[]}) => {
const actionSheetStack = props.stack;

return actionSheetStack.map((node, i) => {
return <React.Fragment key={i}>
{node}
</React.Fragment>;
});
};

export default ActionSheetRenderStack;
Loading

0 comments on commit 25765c4

Please sign in to comment.