File size: 3,816 Bytes
fc69895
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<script lang="ts" module>
	let isOpen = $state(false);

	export function closeMobileNav() {
		isOpen = false;
	}
</script>

<script lang="ts">
	import { browser } from "$app/environment";
	import { beforeNavigate } from "$app/navigation";
	import { base } from "$app/paths";
	import { page } from "$app/state";
	import IconNew from "$lib/components/icons/IconNew.svelte";
	import IconShare from "$lib/components/icons/IconShare.svelte";
	import IconBurger from "$lib/components/icons/IconBurger.svelte";
	import { Spring } from "svelte/motion";
	import { shareModal } from "$lib/stores/shareModal";
	import { loading } from "$lib/stores/loading";
	import { requireAuthUser } from "$lib/utils/auth";
	interface Props {
		title: string | undefined;
		children?: import("svelte").Snippet;
	}

	let { title = $bindable(), children }: Props = $props();

	let closeEl: HTMLButtonElement | undefined = $state();
	let openEl: HTMLButtonElement | undefined = $state();

	const isHuggingChat = $derived(Boolean(page.data?.publicConfig?.isHuggingChat));
	const canShare = $derived(
		isHuggingChat &&
			!$loading &&
			Boolean(page.params?.id) &&
			page.route.id?.startsWith("/conversation/")
	);

	// Define the width for the drawer (less than 100% to create the gap)
	const drawerWidthPercentage = 85;

	const tween = Spring.of(
		() => {
			if (isOpen) {
				return 0 as number;
			}
			return -100 as number;
		},
		{ stiffness: 0.2, damping: 0.8 }
	);

	$effect(() => {
		title ??= "New Chat";
	});

	beforeNavigate(() => {
		isOpen = false;
	});

	let shouldFocusClose = $derived(isOpen && closeEl);
	let shouldRefocusOpen = $derived(!isOpen && browser && document.activeElement === closeEl);

	$effect(() => {
		if (shouldFocusClose) {
			closeEl?.focus();
		} else if (shouldRefocusOpen) {
			openEl?.focus();
		}
	});

	// Function to close the drawer when background is tapped
	function closeDrawer() {
		isOpen = false;
	}
</script>

<nav
	class="flex h-12 items-center justify-between rounded-b-xl border-b bg-gray-50 px-3 dark:border-gray-800 dark:bg-gray-800/30 dark:shadow-xl md:hidden"
>
	<button
		type="button"
		class="-ml-3 flex size-12 shrink-0 items-center justify-center text-lg"
		onclick={() => (isOpen = true)}
		aria-label="Open menu"
		bind:this={openEl}><IconBurger /></button
	>
	<div class="flex h-full items-center justify-center overflow-hidden">
		{#if page.params?.id}
			<span class="max-w-full truncate px-4 first-letter:uppercase" data-testid="chat-title"
				>{title}</span
			>
		{/if}
	</div>
	<div class="flex items-center">
		{#if isHuggingChat}
			<button
				type="button"
				class="flex size-8 shrink-0 items-center justify-center text-lg"
				disabled={!canShare}
				onclick={() => {
					if (!canShare) return;
					shareModal.open();
				}}
				aria-label="Share conversation"
			>
				<IconShare classNames={!canShare ? "opacity-40" : ""} />
			</button>
		{/if}
		<a
			href="{base}/"
			class="flex size-8 shrink-0 items-center justify-center text-lg"
			onclick={(e) => {
				if (requireAuthUser()) {
					e.preventDefault();
				}
			}}
		>
			<IconNew />
		</a>
	</div>
</nav>

<!-- Mobile drawer overlay - shows when drawer is open -->
{#if isOpen}
	<button
		type="button"
		class="fixed inset-0 z-20 cursor-default bg-black/30 md:hidden"
		style="opacity: {Math.max(0, Math.min(1, (100 + tween.current) / 100))};"
		onclick={closeDrawer}
		aria-label="Close mobile navigation"
	></button>
{/if}

<nav
	style="transform: translateX({Math.max(
		-100,
		Math.min(0, tween.current)
	)}%); width: {drawerWidthPercentage}%;"
	class:shadow-[5px_0_15px_0_rgba(0,0,0,0.3)]={isOpen}
	class="fixed bottom-0 left-0 top-0 z-30 grid max-h-screen grid-cols-1
	grid-rows-[auto,1fr,auto,auto] rounded-r-xl bg-white pt-4 dark:bg-gray-900 md:hidden"
>
	{@render children?.()}
</nav>