feat(Footer): create Footer widget with project name and portfolio link

This commit is contained in:
Ilia Mashkov
2026-04-22 13:01:16 +03:00
parent cd8599d5b5
commit 2221ecad4c
4 changed files with 138 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
export { default as Footer } from './ui/Footer.svelte';
@@ -0,0 +1,47 @@
<script module lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf';
import Footer from './Footer.svelte';
const { Story } = defineMeta({
title: 'Widgets/Footer',
component: Footer,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component:
'Application footer with project information and portfolio link. Visible only on desktop screens.',
},
story: { inline: false },
},
layout: 'fullscreen',
},
});
</script>
<Story
name="Desktop View"
parameters={{
viewport: { defaultViewport: 'desktop' },
}}
>
{#snippet template()}
<div class="h-[200px] relative bg-neutral-50 dark:bg-neutral-900">
<Footer />
</div>
{/snippet}
</Story>
<Story
name="Mobile View (Hidden)"
parameters={{
viewport: { defaultViewport: 'mobile1' },
}}
>
{#snippet template()}
<div class="h-[200px] relative bg-neutral-50 dark:bg-neutral-900">
<p class="p-4 text-sm text-neutral-500 italic">Footer should be hidden on mobile.</p>
<Footer />
</div>
{/snippet}
</Story>
+36
View File
@@ -0,0 +1,36 @@
<!--
Widget: Footer
Application footer with project information and portfolio link.
Visible only on desktop screens.
-->
<script lang="ts">
import type { ResponsiveManager } from '$shared/lib/helpers';
import { FooterLink } from '$shared/ui';
import { getContext } from 'svelte';
const responsive = getContext<ResponsiveManager>('responsive');
const currentYear = new Date().getFullYear();
</script>
{#if responsive?.isDesktop || responsive?.isDesktopLarge}
<footer class="fixed bottom-5 right-5 z-50 flex flex-col items-end gap-1 pointer-events-none">
<!-- Portfolio Link (Vertical) -->
<div class="pointer-events-auto">
<FooterLink
text="allmy.work"
href="https://allmy.work/"
target="_blank"
rel="noopener noreferrer"
class="border border-subtle"
/>
</div>
<!-- Project Name (Horizontal) -->
<div class="pointer-events-auto flex items-center gap-2 bg-surface/80 dark:bg-dark-bg/80 backdrop-blur-sm px-3 py-1 border border-subtle">
<div class="w-1.5 h-1.5 bg-brand"></div>
<span class="text-2xs font-mono uppercase tracking-wider-mono text-neutral-500 dark:text-neutral-400">
GlyphDiff © 2025 — {currentYear}
</span>
</div>
</footer>
{/if}
@@ -0,0 +1,54 @@
import {
render,
screen,
} from '@testing-library/svelte';
import { setContext } from 'svelte';
import Footer from './Footer.svelte';
// Mock component to provide context
import ContextWrapper from '$shared/lib/providers/ResponsiveProvider/ResponsiveProvider.svelte';
describe('Footer', () => {
const currentYear = new Date().getFullYear();
it('renders on desktop', () => {
// Mock responsive context
const mockResponsive = {
isDesktop: true,
isDesktopLarge: false,
};
const { container } = render(Footer, {
context: new Map([['responsive', mockResponsive]]),
});
expect(screen.getByText(`GlyphDiff © 2025 — ${currentYear}`)).toBeInTheDocument();
expect(screen.getByText('allmy.work')).toBeInTheDocument();
});
it('renders on large desktop', () => {
const mockResponsive = {
isDesktop: false,
isDesktopLarge: true,
};
render(Footer, {
context: new Map([['responsive', mockResponsive]]),
});
expect(screen.getByText(`GlyphDiff © 2025 — ${currentYear}`)).toBeInTheDocument();
});
it('does not render on mobile or tablet', () => {
const mockResponsive = {
isDesktop: false,
isDesktopLarge: false,
};
render(Footer, {
context: new Map([['responsive', mockResponsive]]),
});
expect(screen.queryByText(/GlyphDiff/)).not.toBeInTheDocument();
});
});