feat(journal): add JournalContext to allow closing panel on mobile
This commit is contained in:
parent
57ffb35f2b
commit
3d957706e9
|
|
@ -0,0 +1,18 @@
|
||||||
|
/**
|
||||||
|
* Journal context — provides `onClose` to message renderers so that
|
||||||
|
* clicking a link message can close the panel on mobile.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createContext, useContext } from "solid-js";
|
||||||
|
|
||||||
|
export interface JournalContextValue {
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const JournalContext = createContext<JournalContextValue>();
|
||||||
|
|
||||||
|
export function useJournalContext(): JournalContextValue | undefined {
|
||||||
|
return useContext(JournalContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { JournalContext };
|
||||||
|
|
@ -26,6 +26,7 @@ import {
|
||||||
} from "../stores/journalStream";
|
} from "../stores/journalStream";
|
||||||
import { StreamView } from "./StreamView";
|
import { StreamView } from "./StreamView";
|
||||||
import { JournalInput } from "./JournalInput";
|
import { JournalInput } from "./JournalInput";
|
||||||
|
import { JournalContext } from "./JournalContext";
|
||||||
|
|
||||||
export interface JournalPanelProps {
|
export interface JournalPanelProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|
@ -57,49 +58,51 @@ export const JournalPanel: Component<JournalPanelProps> = (props) => {
|
||||||
transition-transform duration-300 ease-in-out ${
|
transition-transform duration-300 ease-in-out ${
|
||||||
props.open ? "translate-x-0" : "translate-x-full"
|
props.open ? "translate-x-0" : "translate-x-full"
|
||||||
}`}
|
}`}
|
||||||
aria-hidden={!props.open}
|
inert={!props.open}
|
||||||
>
|
>
|
||||||
{/* Header */}
|
<JournalContext.Provider value={{ onClose: props.onClose }}>
|
||||||
<JournalHeader onClose={props.onClose} />
|
{/* Header */}
|
||||||
|
<JournalHeader onClose={props.onClose} />
|
||||||
|
|
||||||
{/* Player list tabs */}
|
{/* Player list tabs */}
|
||||||
<Show when={stream.connected}>
|
<Show when={stream.connected}>
|
||||||
<div class="flex items-center gap-1 px-3 py-1.5 border-b border-gray-100 bg-gray-50 overflow-x-auto shrink-0">
|
<div class="flex items-center gap-1 px-3 py-1.5 border-b border-gray-100 bg-gray-50 overflow-x-auto shrink-0">
|
||||||
<For each={playerEntries()}>
|
<For each={playerEntries()}>
|
||||||
{([name]) => (
|
{([name]) => (
|
||||||
<span class="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full whitespace-nowrap">
|
<span class="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full whitespace-nowrap">
|
||||||
{name}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowInvite(true)}
|
onClick={() => setShowInvite(true)}
|
||||||
class="text-xs text-gray-400 hover:text-blue-600 px-1.5 py-0.5 rounded border border-dashed border-gray-300 hover:border-blue-400 whitespace-nowrap shrink-0"
|
class="text-xs text-gray-400 hover:text-blue-600 px-1.5 py-0.5 rounded border border-dashed border-gray-300 hover:border-blue-400 whitespace-nowrap shrink-0"
|
||||||
title="邀请玩家"
|
title="邀请玩家"
|
||||||
>
|
>
|
||||||
+ 邀请
|
+ 邀请
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<Show
|
|
||||||
when={stream.connected}
|
|
||||||
fallback={
|
|
||||||
<div class="flex-1 flex items-center justify-center p-4 overflow-y-auto">
|
|
||||||
<ConnectDialog />
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</Show>
|
||||||
>
|
|
||||||
<div class="flex-1 min-h-0">
|
|
||||||
<StreamView />
|
|
||||||
</div>
|
|
||||||
<JournalInput />
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
{/* Invite modal */}
|
<Show
|
||||||
<Show when={showInvite()}>
|
when={stream.connected}
|
||||||
<InviteDialog onClose={() => setShowInvite(false)} />
|
fallback={
|
||||||
</Show>
|
<div class="flex-1 flex items-center justify-center p-4 overflow-y-auto">
|
||||||
|
<ConnectDialog />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div class="flex-1 min-h-0">
|
||||||
|
<StreamView />
|
||||||
|
</div>
|
||||||
|
<JournalInput />
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
{/* Invite modal */}
|
||||||
|
<Show when={showInvite()}>
|
||||||
|
<InviteDialog onClose={() => setShowInvite(false)} />
|
||||||
|
</Show>
|
||||||
|
</JournalContext.Provider>
|
||||||
</aside>
|
</aside>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -44,3 +44,5 @@ export { ComposePanel } from "./ComposePanel";
|
||||||
export { JournalInput } from "./JournalInput";
|
export { JournalInput } from "./JournalInput";
|
||||||
export { DynamicForm } from "./DynamicForm";
|
export { DynamicForm } from "./DynamicForm";
|
||||||
export { useJournalCompletions, invalidateCompletions } from "./completions";
|
export { useJournalCompletions, invalidateCompletions } from "./completions";
|
||||||
|
export { JournalContext, useJournalContext } from "./JournalContext";
|
||||||
|
export type { JournalContextValue } from "./JournalContext";
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { z } from "zod";
|
||||||
import { registerMessageType } from "../registry";
|
import { registerMessageType } from "../registry";
|
||||||
import { journalSetState } from "../../stores/journalStream";
|
import { journalSetState } from "../../stores/journalStream";
|
||||||
import { produce } from "solid-js/store";
|
import { produce } from "solid-js/store";
|
||||||
|
import { useJournalContext } from "../JournalContext";
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
path: z.string().min(1),
|
path: z.string().min(1),
|
||||||
|
|
@ -44,6 +45,7 @@ function slugToTitle(slug: string): string {
|
||||||
|
|
||||||
const RevealLink: Component<LinkPayload> = (p) => {
|
const RevealLink: Component<LinkPayload> = (p) => {
|
||||||
const navigate = useNavigateWithParams();
|
const navigate = useNavigateWithParams();
|
||||||
|
const ctx = useJournalContext();
|
||||||
|
|
||||||
const target = () => {
|
const target = () => {
|
||||||
let t = encodePath(p.path);
|
let t = encodePath(p.path);
|
||||||
|
|
@ -59,6 +61,10 @@ const RevealLink: Component<LinkPayload> = (p) => {
|
||||||
const handleClick = (e: MouseEvent) => {
|
const handleClick = (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
navigate(target());
|
navigate(target());
|
||||||
|
// On mobile, close the journal panel to reveal the article
|
||||||
|
if (window.innerWidth < 768) {
|
||||||
|
ctx?.onClose();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue