Skip to content

Commit

Permalink
Merge pull request #1204 from rainlanguage/1182-mobile-styling-for-al…
Browse files Browse the repository at this point in the history
…l-pages

Mobile styling for Sidebar
  • Loading branch information
hardyjosh authored Jan 23, 2025
2 parents 0279838 + bce5f80 commit 58fc22e
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 73 deletions.
6 changes: 6 additions & 0 deletions packages/webapp/src/lib/__mocks__/MockComponent.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script lang="ts">
import { props } from './MockComponent';
// whenever the props update, we want to update the store with those
$: $props = $$props;
</script>
3 changes: 3 additions & 0 deletions packages/webapp/src/lib/__mocks__/MockComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { writable } from 'svelte/store';

export const props = writable<unknown>();
187 changes: 120 additions & 67 deletions packages/webapp/src/lib/components/Sidebar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,81 +4,134 @@
SidebarGroup,
SidebarItem,
SidebarWrapper,
SidebarBrand
SidebarBrand,
CloseButton,
Button
} from 'flowbite-svelte';
import { WalletSolid, ReceiptSolid, FileLinesSolid, PlusOutline } from 'flowbite-svelte-icons';
import { page } from '$app/stores';
import {
WalletSolid,
ReceiptSolid,
FileLinesSolid,
PlusOutline,
BarsSolid
} from 'flowbite-svelte-icons';
import {
ButtonDarkMode,
IconTelegram,
IconExternalLink,
logoDark,
logoLight
} from '@rainlanguage/ui-components';
import { onMount } from 'svelte';
export let colorTheme;
export let page;
let sideBarHidden: boolean = false;
let breakPoint: number = 1024;
let width: number;
$: sideBarHidden = width < breakPoint;
onMount(() => {
sideBarHidden = width < breakPoint;
});
const toggleSide = () => {
if (width < breakPoint) {
sideBarHidden = !sideBarHidden;
}
};
</script>

<Sidebar activeUrl={$page.url.pathname} asideClass="w-64 fixed z-10">
<SidebarWrapper divClass="overflow-y-auto py-11 px-3 bg-gray-100 dark:bg-gray-800 min-h-screen">
<SidebarGroup ulClass="">
<SidebarBrand
site={{
name: '',
href: '/',
img: $colorTheme == 'light' ? logoLight : logoDark
}}
imgClass="m-auto"
></SidebarBrand>
</SidebarGroup>
<SidebarGroup border>
<SidebarItem label="Deploy" href="/deploy">
<svelte:fragment slot="icon">
<PlusOutline class="h-5 w-5" />
<span data-testid="sidebar-deploy"></span>
</svelte:fragment>
</SidebarItem>
</SidebarGroup>
<SidebarGroup border>
<SidebarItem label="Orders" href="/orders">
<svelte:fragment slot="icon">
<ReceiptSolid class="h-5 w-5" />
<span data-testid="sidebar-orders"></span>
</svelte:fragment>
</SidebarItem>
<SidebarItem label="Vaults" href="/vaults">
<svelte:fragment slot="icon">
<WalletSolid class="h-5 w-5" />
<span data-testid="sidebar-vaults"></span>
</svelte:fragment>
</SidebarItem>
</SidebarGroup>
<SidebarGroup border>
<SidebarItem
label="Documentation"
target="_blank"
href="https://docs.rainlang.xyz/raindex/overview"
>
<svelte:fragment slot="icon">
<IconExternalLink />
<span data-testid="sidebar-documentation"></span>
</svelte:fragment>
</SidebarItem>
<SidebarItem label="Ask for help" target="_blank" href="https://t.me/+W0aQ36ptN_E2MjZk">
<svelte:fragment slot="icon">
<IconTelegram />
<span data-testid="sidebar-telegram"></span>
</svelte:fragment>
</SidebarItem>
<SidebarItem label="License" href="/license">
<svelte:fragment slot="icon">
<FileLinesSolid />
<span data-testid="sidebar-license"></span>
</svelte:fragment>
</SidebarItem>
</SidebarGroup>
<SidebarGroup border class="flex justify-start">
<ButtonDarkMode {colorTheme} />
</SidebarGroup>
</SidebarWrapper>
</Sidebar>
<svelte:window bind:innerWidth={width} />
<div>
{#if sideBarHidden}
<Button
on:click={() => (sideBarHidden = false)}
color="alternative"
class="absolute left-2 top-2 flex size-8 items-center p-5 lg:hidden"
data-testid="sidebar-bars"
>
<BarsSolid class="" />
</Button>
{/if}
<Sidebar
activeUrl={page.url.pathname}
asideClass="w-64 z-10 fixed"
bind:hidden={sideBarHidden}
data-testid="sidebar"
>
{#if !sideBarHidden}
<CloseButton
data-testid="close-button"
class="absolute right-3 top-2 z-20 flex size-8 items-center lg:hidden"
on:click={() => (sideBarHidden = true)}
/>
{/if}
<SidebarWrapper divClass="overflow-y-auto py-11 px-3 bg-gray-100 dark:bg-gray-800 min-h-screen">
<SidebarGroup ulClass="">
<SidebarBrand
site={{
name: '',
href: '/',
img: $colorTheme === 'light' ? logoLight : logoDark
}}
imgClass="m-auto"
aClass="mb-0"
></SidebarBrand>
</SidebarGroup>
<SidebarGroup border>
<SidebarItem label="Deploy" href="/deploy" on:click={toggleSide}>
<svelte:fragment slot="icon">
<PlusOutline class="h-5 w-5" />
<span data-testid="sidebar-deploy"></span>
</svelte:fragment>
</SidebarItem>
</SidebarGroup>
<SidebarGroup border>
<SidebarItem label="Orders" href="/orders" on:click={toggleSide}>
<svelte:fragment slot="icon">
<ReceiptSolid class="h-5 w-5" />
<span data-testid="sidebar-orders"></span>
</svelte:fragment>
</SidebarItem>
<SidebarItem label="Vaults" href="/vaults" on:click={toggleSide}>
<svelte:fragment slot="icon">
<WalletSolid class="h-5 w-5" />
<span data-testid="sidebar-vaults"></span>
</svelte:fragment>
</SidebarItem>
</SidebarGroup>
<SidebarGroup border>
<SidebarItem
on:click={toggleSide}
label="Documentation"
target="_blank"
href="https://docs.rainlang.xyz/raindex/overview"
>
<svelte:fragment slot="icon">
<IconExternalLink />
<span data-testid="sidebar-documentation"></span>
</svelte:fragment>
</SidebarItem>
<SidebarItem
on:click={toggleSide}
label="Ask for help"
target="_blank"
href="https://t.me/+W0aQ36ptN_E2MjZk"
>
<svelte:fragment slot="icon">
<IconTelegram />
<span data-testid="sidebar-telegram"></span>
</svelte:fragment>
</SidebarItem>
<SidebarItem on:click={toggleSide} label="License" href="/license">
<svelte:fragment slot="icon">
<FileLinesSolid />
<span data-testid="sidebar-license"></span>
</svelte:fragment>
</SidebarItem>
</SidebarGroup>
<SidebarGroup border class="flex justify-start">
<ButtonDarkMode {colorTheme} />
</SidebarGroup>
</SidebarWrapper>
</Sidebar>
</div>
5 changes: 3 additions & 2 deletions packages/webapp/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { QueryClient, QueryClientProvider } from '@tanstack/svelte-query';
import Sidebar from '$lib/components/Sidebar.svelte';
import { colorTheme } from '$lib/darkMode';
import { page } from '$app/stores';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
Expand All @@ -14,8 +15,8 @@

<QueryClientProvider client={queryClient}>
<div class="flex min-h-screen w-full justify-start bg-white dark:bg-gray-900 dark:text-gray-400">
<Sidebar {colorTheme} />
<main class="ml-64 h-full w-full grow overflow-x-auto p-8">
<Sidebar {colorTheme} page={$page} />
<main class="mx-auto h-full w-full grow overflow-x-auto pl-20 pt-8 lg:ml-64 lg:p-8">
<slot />
</main>
</div>
Expand Down
111 changes: 111 additions & 0 deletions packages/webapp/src/tests/Sidebar.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { render, cleanup, screen, fireEvent, waitFor } from '@testing-library/svelte';
import { vi, describe, it, expect, afterEach } from 'vitest';
import Sidebar from '../lib/components/Sidebar.svelte';
import { writable } from 'svelte/store';

vi.mock('@rainlanguage/ui-components', async () => {
const MockComponent = (await import('../lib/__mocks__/MockComponent.svelte')).default;
return {
ButtonDarkMode: MockComponent,
logoLight: 'mock-logo-light.svg',
logoDark: 'mock-logo-dark.svg',
IconTelegram: MockComponent,
IconExternalLink: MockComponent
};
});

vi.mock('svelte/store', () => {
return {
writable: () => ({
subscribe: () => {
return () => {};
},
set: vi.fn()
})
};
});

const mockWindowSize = (width: number) => {
Object.defineProperty(window, 'innerWidth', { writable: true, configurable: true, value: width });
window.dispatchEvent(new Event('resize'));
};

describe('Sidebar', () => {
afterEach(() => {
cleanup();
});

it('renders correctly with colorTheme store', async () => {
const mockColorTheme = writable('light');
const mockPage = {
url: { pathname: '/' }
};
const { container } = render(Sidebar, {
props: {
colorTheme: mockColorTheme,
page: mockPage
}
});

expect(container).toBeTruthy();
});
it('renders menu bars button when screen width is small', () => {
// Mock small screen width
mockWindowSize(500);
const mockColorTheme = writable('light');
const mockPage = {
url: { pathname: '/' }
};
render(Sidebar, { colorTheme: mockColorTheme, page: mockPage });

const barsButton = screen.getByTestId('sidebar-bars');
expect(barsButton).toBeInTheDocument();
});
it('shows sidebar on wide screen', () => {
mockWindowSize(1025);
const mockColorTheme = writable('light');
const mockPage = {
url: { pathname: '/' }
};
render(Sidebar, { colorTheme: mockColorTheme, page: mockPage });

expect(screen.getByTestId('sidebar')).toBeInTheDocument();
});
it('renders sidebar when bars button is clicked', async () => {
// Mock small screen width
mockWindowSize(500);
const mockColorTheme = writable('light');
const mockPage = {
url: { pathname: '/' }
};
render(Sidebar, { colorTheme: mockColorTheme, page: mockPage });

const barsButton = screen.getByTestId('sidebar-bars');
fireEvent.click(barsButton);

await waitFor(() => {
const sidebar = screen.getByTestId('sidebar');
expect(sidebar.hidden).toBe(false);
});
});
it('hides sidebar when close button is clicked', async () => {
mockWindowSize(500);
const mockColorTheme = writable('light');
const mockPage = {
url: { pathname: '/' }
};
render(Sidebar, { colorTheme: mockColorTheme, page: mockPage });

const barsButton = screen.getByTestId('sidebar-bars');
await fireEvent.click(barsButton);
await waitFor(() => {
expect(screen.getByTestId('sidebar')).toBeInTheDocument();
});
const closeButton = screen.getByTestId('close-button');
await fireEvent.click(closeButton);
await waitFor(() => {
const sidebar = screen.getByTestId('sidebar');
expect(sidebar.hidden).toBe(true);
});
});
});
7 changes: 7 additions & 0 deletions packages/webapp/test-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import '@testing-library/jest-dom/vitest';
import { vi } from 'vitest';

// Mock for codemirror-rainlang
vi.mock('codemirror-rainlang', () => ({
RainlangLR: vi.fn()
}));
10 changes: 7 additions & 3 deletions packages/webapp/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowImportingTsExtensions": true,
"allowArbitraryExtensions": true,
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
Expand All @@ -9,11 +11,13 @@
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
"module": "es2022",
"moduleResolution": "bundler",
"types": ["vitest/globals", "@testing-library/jest-dom", "vitest/importMeta", "codemirror-rainlang", "@rainlanguage/dotrain"]
},
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}
}
Loading

0 comments on commit 58fc22e

Please sign in to comment.