- {props.icon}
+ {props.showBackButton && (
+
+
+
+ )}
{props.pageTitle}
diff --git a/components/ActionBar/NavigationButtons.tsx b/components/ActionBar/NavigationButtons.tsx
index 496f6bf5..5a16fe1a 100644
--- a/components/ActionBar/NavigationButtons.tsx
+++ b/components/ActionBar/NavigationButtons.tsx
@@ -50,7 +50,12 @@ export const WriterButton = () => {
let current = useIsOnWriterPage();
return (
- } label="Write" active={current} />
+ }
+ label="Write"
+ active={current}
+ />
);
};
@@ -59,7 +64,12 @@ export const ReaderButton = (props: { subs: boolean }) => {
let current = useIsActive("/reader");
return (
- } label="Read" active={current} />
+ }
+ label="Read"
+ active={current}
+ />
);
};
diff --git a/components/ActionBar/Publications.tsx b/components/ActionBar/Publications.tsx
index f8eb646f..23f388c0 100644
--- a/components/ActionBar/Publications.tsx
+++ b/components/ActionBar/Publications.tsx
@@ -41,6 +41,7 @@ export const PublicationButtons = (props: { className?: string }) => {
+
PUBLICATIONS
{hasLooseleafs && (
<>
@@ -108,10 +109,13 @@ const PubListEmpty = () => {
return (
}
subtext="Start a blog on ATProto!"
- onClick={() => {
+ onClick={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
setState("info");
}}
/>
@@ -127,6 +131,7 @@ const PubListEmpty = () => {
asChild
trigger={
}
@@ -173,7 +178,7 @@ export const PubListEmptyContent = (props: { compact?: boolean }) => {
noEmailLogin
asChild
trigger={
-
+
{identity ? "Link to" : "Log in with"} Atmosphere
}
diff --git a/components/Icons/GoToArrowLined.tsx b/components/Icons/GoToArrowLined.tsx
new file mode 100644
index 00000000..e0d5736a
--- /dev/null
+++ b/components/Icons/GoToArrowLined.tsx
@@ -0,0 +1,22 @@
+import { Props } from "./Props";
+
+export const GoToArrowLined = (props: Props) => {
+ return (
+
+ );
+};
diff --git a/components/Modal.tsx b/components/Modal.tsx
index 893aad65..5bd2df16 100644
--- a/components/Modal.tsx
+++ b/components/Modal.tsx
@@ -1,6 +1,9 @@
+"use client";
import * as Dialog from "@radix-ui/react-dialog";
import React from "react";
+import { isIOS } from "src/utils/isDevice";
import { CloseTiny } from "./Icons/CloseTiny";
+import { useVisualViewport } from "./ViewportSizeLayout";
export const Modal = ({
className,
@@ -19,14 +22,33 @@ export const Modal = ({
title?: React.ReactNode;
children: React.ReactNode;
}) => {
+ let { height, offsetTop, difference } = useVisualViewport();
+ // iOS keyboard open: re-center modal against the visual viewport. Android
+ // resizes the layout viewport via interactiveWidget: "resizes-content".
+ let keyboardOpen = isIOS() && difference !== 0 && height > 0;
return (
{trigger !== undefined && (
{trigger}
)}
-
+
- {props.children}
-
- );
-}
-
-function useViewportDifference(): number {
- let [difference, setDifference] = useState(0);
-
- useEffect(() => {
- // Use visualViewport api to track available height even on iOS virtual keyboard opening
- let onResize = () => {
- setDifference(window.innerHeight - getViewportSize().height);
- };
-
- if (!visualViewport) {
- window.addEventListener("resize", onResize);
- } else {
- visualViewport.addEventListener("resize", onResize);
- }
-
- return () => {
- if (!visualViewport) {
- window.removeEventListener("resize", onResize);
- } else {
- visualViewport.removeEventListener("resize", onResize);
- }
- };
- }, []);
-
- return difference;
-}
+type VisualViewportState = {
+ width: number;
+ height: number;
+ offsetTop: number;
+ difference: number;
+};
-function getViewportSize() {
- if (typeof window === "undefined") return { width: 0, height: 0 };
+function getVisualViewport(): VisualViewportState {
+ if (typeof window === "undefined") {
+ return { width: 0, height: 0, offsetTop: 0, difference: 0 };
+ }
+ let height = visualViewport?.height || window.innerHeight;
return {
- width: visualViewport?.width || window?.innerWidth,
- height: visualViewport?.height || window?.innerHeight,
+ width: visualViewport?.width || window.innerWidth,
+ height,
+ offsetTop: visualViewport?.offsetTop || 0,
+ difference: window.innerHeight - height,
};
}
-export function useViewportSize(): {
- width: number;
- height: number;
-} {
- let [size, setSize] = useState(() => getViewportSize());
+export function useVisualViewport(): VisualViewportState {
+ let [state, setState] = useState(getVisualViewport);
useEffect(() => {
- // Use visualViewport api to track available height even on iOS virtual keyboard opening
let onResize = () => {
- setSize((size) => {
- let newSize = getViewportSize();
- if (newSize.width === size.width && newSize.height === size.height) {
- return size;
+ setState((prev) => {
+ let next = getVisualViewport();
+ if (
+ prev.width === next.width &&
+ prev.height === next.height &&
+ prev.offsetTop === next.offsetTop
+ ) {
+ return prev;
}
- return newSize;
+ return next;
});
};
- if (!visualViewport) {
- window.addEventListener("resize", onResize);
- } else {
- visualViewport.addEventListener("resize", onResize);
- }
-
- return () => {
- if (!visualViewport) {
- window.removeEventListener("resize", onResize);
- } else {
- visualViewport.removeEventListener("resize", onResize);
- }
- };
+ let target: VisualViewport | Window = visualViewport || window;
+ target.addEventListener("resize", onResize);
+ return () => target.removeEventListener("resize", onResize);
}, []);
- return size;
+ return state;
+}
+
+export function ViewportSizeLayout(props: { children: React.ReactNode }) {
+ let { height, difference } = useVisualViewport();
+ return (
+
+ {props.children}
+
+ );
}