From 5acb6af50a9f3fa8d73372c6d000c8c88592f966 Mon Sep 17 00:00:00 2001
From: Nicola Clark <nicola@slottedspoon.dev>
Date: Sat, 12 Oct 2024 00:53:39 -0500
Subject: [PATCH] add mobile nav toggle

---
 playwright.config.ts                 |  6 ++++++
 src/lib/components/nav-toggle.svelte | 29 ++++++++++++++++++++++++++++
 src/lib/components/nav-tree.svelte   | 18 +++++++++++++++--
 src/styles.less                      |  3 +++
 tests/desktop.test.ts                | 11 +++++++++++
 tests/{test.ts => general.test.ts}   |  5 -----
 tests/mobile.test.ts                 | 21 ++++++++++++++++++++
 7 files changed, 86 insertions(+), 7 deletions(-)
 create mode 100644 src/lib/components/nav-toggle.svelte
 create mode 100644 tests/desktop.test.ts
 rename tests/{test.ts => general.test.ts} (84%)
 create mode 100644 tests/mobile.test.ts

diff --git a/playwright.config.ts b/playwright.config.ts
index b59a0e4..a7caf96 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -7,6 +7,12 @@ const config: PlaywrightTestConfig = {
   },
   testDir: 'tests',
   testMatch: /(.+\.)?(test|spec)\.[jt]s/,
+  use: {
+    viewport: {
+      height: 900,
+      width: 1600,
+    },
+  },
 };
 
 export default config;
diff --git a/src/lib/components/nav-toggle.svelte b/src/lib/components/nav-toggle.svelte
new file mode 100644
index 0000000..5908e1a
--- /dev/null
+++ b/src/lib/components/nav-toggle.svelte
@@ -0,0 +1,29 @@
+<script lang="ts">
+  export let navOpen: boolean;
+
+  // if nav is open, we should "hide" it on press. if not, we should "show" it.
+  let action: string;
+  $: action = navOpen ? 'hide' : 'show';
+
+  function toggle() {
+    navOpen = !navOpen;
+  }
+</script>
+
+<button on:click={toggle}>{action} navigation</button>
+
+<style lang="less">
+  button {
+    background-color: lighten(@color-dark, 10%);
+    border: none;
+    color: @color-light;
+    font-size: @font-s-md;
+    font-weight: @weight-semibold;
+    padding: @padding-sm;
+    width: 100%;
+
+    @media screen and (min-width: @sizes[lg]) {
+      display: none;
+    }
+  }
+</style>
diff --git a/src/lib/components/nav-tree.svelte b/src/lib/components/nav-tree.svelte
index 17420e7..47fa73f 100644
--- a/src/lib/components/nav-tree.svelte
+++ b/src/lib/components/nav-tree.svelte
@@ -1,5 +1,15 @@
+<script lang="ts">
+  import NavToggle from './nav-toggle.svelte';
+
+  let open: boolean = false;
+
+  let hidden: boolean;
+  $: hidden = !open;
+</script>
+
 <nav>
-  <ul id="nav-tree" role="tablist">
+  <NavToggle bind:navOpen={open} />
+  <ul class:hidden role="tablist">
     <slot />
   </ul>
 </nav>
@@ -11,9 +21,13 @@
     margin: 0;
     padding: 0;
 
+    &.hidden {
+      display: none;
+    }
+
     @media screen and (min-width: @sizes[lg]) {
       align-items: center;
-      display: flex;
+      display: flex !important;
       flex-direction: row;
       justify-content: space-evenly;
     }
diff --git a/src/styles.less b/src/styles.less
index 7abc492..2c35fe9 100644
--- a/src/styles.less
+++ b/src/styles.less
@@ -20,6 +20,9 @@
 @padding-lg: @padding-lg-y @padding-lg-x;
 @padding-lg-x: 1.5em;
 @padding-lg-y: 1em;
+@padding-sm: @padding-sm-y @padding-sm-x;
+@padding-sm-x: 0.875em;
+@padding-sm-y: 0.375em;
 @padding-xl: @padding-xl-y @padding-xl-x;
 @padding-xl-x: 2em;
 @padding-xl-y: 1.5em;
diff --git a/tests/desktop.test.ts b/tests/desktop.test.ts
new file mode 100644
index 0000000..249d003
--- /dev/null
+++ b/tests/desktop.test.ts
@@ -0,0 +1,11 @@
+import { expect, test } from '@playwright/test';
+
+test('desktop page has nav tree', async ({ page }) => {
+  await page.goto('/');
+  await expect(page.locator('nav')).toBeVisible();
+});
+
+test('desktop page does not have nav toggle', async ({ page }) => {
+  await page.goto('/');
+  await expect(page.locator('nav button')).toBeHidden();
+});
diff --git a/tests/test.ts b/tests/general.test.ts
similarity index 84%
rename from tests/test.ts
rename to tests/general.test.ts
index ef21675..a424975 100644
--- a/tests/test.ts
+++ b/tests/general.test.ts
@@ -7,11 +7,6 @@ test('page has headline', async ({ page }) => {
   await expect(headline).toHaveText('resumarkdown');
 });
 
-test('page has nav tree', async ({ page }) => {
-  await page.goto('/');
-  await expect(page.locator('nav')).toBeVisible();
-});
-
 test('nav items work', async ({ page }) => {
   await page.goto('/');
   let lastPane = '#pane-preview';
diff --git a/tests/mobile.test.ts b/tests/mobile.test.ts
new file mode 100644
index 0000000..4fd31e7
--- /dev/null
+++ b/tests/mobile.test.ts
@@ -0,0 +1,21 @@
+import { expect, test } from '@playwright/test';
+
+test.use({ viewport: { height: 2556, width: 1179 } });
+
+test('mobile page has nav tree hidden by default', async ({ page }) => {
+  await page.goto('/');
+  await expect(page.locator('nav ul')).toBeHidden();
+});
+
+test('mobile page has nav toggle', async ({ page }) => {
+  await page.goto('/');
+  await expect(page.locator('nav button')).toBeVisible();
+});
+
+test('nav toggle works', async ({ page }) => {
+  await page.goto('/');
+  await page.locator('nav button').click();
+  await expect(page.locator('nav ul')).toBeVisible();
+  await page.locator('nav button').click();
+  await expect(page.locator('nav ul')).toBeHidden();
+});
