feat(FooterLink): move FooterLink to the Footer widget layer, delete the one in shared/ui

This commit is contained in:
Ilia Mashkov
2026-04-23 13:07:16 +03:00
parent 7a510e7acf
commit c79eb3f815
5 changed files with 28 additions and 131 deletions
@@ -1,53 +0,0 @@
<script module lang="ts">
import { defineMeta } from '@storybook/addon-svelte-csf';
import type { ComponentProps } from 'svelte';
import FooterLink from './FooterLink.svelte';
const { Story } = defineMeta({
title: 'Shared/FooterLink',
component: FooterLink,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component: 'Standard footer link with arrow icon and hover effects.',
},
story: { inline: false },
},
layout: 'centered',
},
argTypes: {
text: {
control: 'text',
description: 'Link text',
},
href: {
control: 'text',
description: 'Link URL',
},
},
});
</script>
<Story
name="Default"
args={{
text: 'Google Fonts',
href: 'https://fonts.google.com',
target: '_blank',
}}
>
{#snippet template(args: ComponentProps<typeof FooterLink>)}
<FooterLink {...args} />
{/snippet}
</Story>
<Story name="Multiple Links">
{#snippet template()}
<div class="flex gap-4 p-8 bg-neutral-100 dark:bg-neutral-900 rounded-lg">
<FooterLink text="Google Fonts" href="https://fonts.google.com" target="_blank" />
<FooterLink text="Fontshare" href="https://www.fontshare.com" target="_blank" />
<FooterLink text="GitHub" href="https://github.com" target="_blank" />
</div>
{/snippet}
</Story>
@@ -1,58 +0,0 @@
import {
render,
screen,
} from '@testing-library/svelte';
import FooterLink from './FooterLink.svelte';
describe('FooterLink', () => {
const defaultProps = {
text: 'Google Fonts',
href: 'https://fonts.google.com',
};
describe('Rendering', () => {
it('renders text content', () => {
render(FooterLink, { props: defaultProps });
expect(screen.getByText('Google Fonts')).toBeInTheDocument();
});
it('renders as an anchor element with correct href', () => {
render(FooterLink, { props: defaultProps });
const link = screen.getByRole('link');
expect(link).toBeInTheDocument();
expect(link).toHaveAttribute('href', 'https://fonts.google.com');
});
it('renders the arrow icon', () => {
const { container } = render(FooterLink, { props: defaultProps });
const icon = container.querySelector('svg');
expect(icon).toBeInTheDocument();
expect(icon).toHaveClass('lucide-arrow-up-right');
});
});
describe('Attributes', () => {
it('applies custom CSS classes', () => {
render(FooterLink, {
props: {
...defaultProps,
class: 'custom-class',
},
});
expect(screen.getByRole('link')).toHaveClass('custom-class');
});
it('spreads additional anchor attributes', () => {
render(FooterLink, {
props: {
...defaultProps,
target: '_blank',
rel: 'noopener',
},
});
const link = screen.getByRole('link');
expect(link).toHaveAttribute('target', '_blank');
expect(link).toHaveAttribute('rel', 'noopener');
});
});
});
+6 -6
View File
@@ -52,12 +52,6 @@ export {
*/ */
default as FilterGroup, default as FilterGroup,
} from './FilterGroup/FilterGroup.svelte'; } from './FilterGroup/FilterGroup.svelte';
export {
/**
* Standard footer link with arrow icon and hover effects
*/
default as FooterLink,
} from './FooterLink/FooterLink.svelte';
export { export {
/** /**
* Small text for secondary meta-information * Small text for secondary meta-information
@@ -76,6 +70,12 @@ export {
*/ */
default as Label, default as Label,
} from './Label/Label.svelte'; } from './Label/Label.svelte';
export {
/**
* Styled link with optional icon
*/
default as Link,
} from './Link/Link.svelte';
export { export {
/** /**
* Full-page or component-level progress spinner * Full-page or component-level progress spinner
+2 -1
View File
@@ -6,8 +6,8 @@
<script lang="ts"> <script lang="ts">
import { cn } from '$shared/lib'; import { cn } from '$shared/lib';
import type { ResponsiveManager } from '$shared/lib/helpers'; import type { ResponsiveManager } from '$shared/lib/helpers';
import { FooterLink } from '$shared/ui';
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import FooterLink from './FooterLink.svelte';
const responsive = getContext<ResponsiveManager>('responsive'); const responsive = getContext<ResponsiveManager>('responsive');
const isVertical = $derived(responsive?.isDesktop || responsive?.isDesktopLarge); const isVertical = $derived(responsive?.isDesktop || responsive?.isDesktopLarge);
@@ -41,6 +41,7 @@ const currentYear = new Date().getFullYear();
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class={cn('border border-subtle', isVertical ? 'text-2xs' : 'text-4xs')} class={cn('border border-subtle', isVertical ? 'text-2xs' : 'text-4xs')}
iconClass={isVertical ? 'rotate-90' : ''}
/> />
</div> </div>
</footer> </footer>
@@ -1,9 +1,11 @@
<!-- <!--
Component: FooterLink Component: FooterLink
Standard footer link with arrow icon and hover effects. Specific footer link implementation that uses the generic Link component
and adds the default arrow icon.
--> -->
<script lang="ts"> <script lang="ts">
import { cn } from '$shared/lib'; import { cn } from '$shared/lib';
import { Link } from '$shared/ui';
import ArrowUpRightIcon from '@lucide/svelte/icons/arrow-up-right'; import ArrowUpRightIcon from '@lucide/svelte/icons/arrow-up-right';
import type { HTMLAnchorAttributes } from 'svelte/elements'; import type { HTMLAnchorAttributes } from 'svelte/elements';
@@ -12,6 +14,10 @@ interface Props extends HTMLAnchorAttributes {
* Link text * Link text
*/ */
text: string; text: string;
/**
* CSS classes for the default icon
*/
iconClass?: string;
/** /**
* Link URL * Link URL
*/ */
@@ -24,25 +30,26 @@ interface Props extends HTMLAnchorAttributes {
let { let {
text, text,
iconClass,
href, href,
class: className, class: className,
...rest ...rest
}: Props = $props(); }: Props = $props();
</script> </script>
<a <Link
{href} {href}
class={cn( class={className}
'group inline-flex items-center gap-1 text-2xs font-mono uppercase tracking-wider-mono',
'text-neutral-400 hover:text-brand transition-colors',
'bg-surface/80 dark:bg-dark-bg/80 backdrop-blur-sm px-2 py-1 pointer-events-auto',
className,
)}
{...rest} {...rest}
> >
<span>{text}</span> <span>{text}</span>
<ArrowUpRightIcon {#snippet icon()}
size={10} <ArrowUpRightIcon
class="fill-body group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200" size={10}
/> class={cn(
</a> 'fill-body group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200',
iconClass,
)}
/>
{/snippet}
</Link>