feat: social links with inline SVG icons from CMS
SocialRecord gains icon field (SVG markup string). InlineSvg component parses SVG string via html-react-parser. Footer renders icon on mobile (sm:hidden label), label on sm+ (hidden icon). Email field refactored from string to SocialRecord relation.
This commit is contained in:
+10
-2
@@ -141,6 +141,10 @@ export type SocialRecord = BaseRecord & {
|
||||
* Full URL for the social profile
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* SVG markup string stored in PocketBase
|
||||
*/
|
||||
icon: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -149,7 +153,7 @@ export type SocialRecord = BaseRecord & {
|
||||
*/
|
||||
export type ContactsRecord = BaseRecord & {
|
||||
/**
|
||||
* Primary contact email address
|
||||
* Raw relation ID — use expand?.email for the resolved record
|
||||
*/
|
||||
email: string;
|
||||
/**
|
||||
@@ -157,9 +161,13 @@ export type ContactsRecord = BaseRecord & {
|
||||
*/
|
||||
socials: string[];
|
||||
/**
|
||||
* Expanded relation data, present when fetched with expand=socials
|
||||
* Expanded relation data, present when fetched with expand=email,socials
|
||||
*/
|
||||
expand?: {
|
||||
/**
|
||||
* Resolved email contact record
|
||||
*/
|
||||
email?: SocialRecord;
|
||||
/**
|
||||
* Resolved social link records
|
||||
*/
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { InlineSvg } from './ui/InlineSvg';
|
||||
@@ -0,0 +1,25 @@
|
||||
import parse from 'html-react-parser';
|
||||
import { cn } from '$shared/lib';
|
||||
|
||||
type Props = {
|
||||
/**
|
||||
* SVG markup string to inline as React elements
|
||||
*/
|
||||
svg: string;
|
||||
/**
|
||||
* Additional CSS classes on the wrapper span
|
||||
*/
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses an SVG markup string into React elements.
|
||||
* Inherits color from parent via currentColor.
|
||||
*/
|
||||
export function InlineSvg({ svg, className }: Props) {
|
||||
if (!svg) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <span className={cn('inline-flex items-center', className)}>{parse(svg)}</span>;
|
||||
}
|
||||
@@ -21,9 +21,19 @@ const mockSettings = {
|
||||
collectionName: 'contacts',
|
||||
created: '',
|
||||
updated: '',
|
||||
email: 'hello@allmy.work',
|
||||
email: 'e1',
|
||||
socials: ['s1'],
|
||||
expand: {
|
||||
email: {
|
||||
id: 'e1',
|
||||
collectionId: 'contact',
|
||||
collectionName: 'contact',
|
||||
created: '',
|
||||
updated: '',
|
||||
label: 'hello@allmy.work',
|
||||
url: 'mailto:hello@allmy.work',
|
||||
icon: '',
|
||||
},
|
||||
socials: [
|
||||
{
|
||||
id: 's1',
|
||||
@@ -33,6 +43,7 @@ const mockSettings = {
|
||||
updated: '',
|
||||
label: 'GitHub',
|
||||
url: 'https://github.com',
|
||||
icon: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -58,19 +69,28 @@ describe('Footer', () => {
|
||||
});
|
||||
|
||||
describe('email link', () => {
|
||||
it('renders the contact email as a mailto link', async () => {
|
||||
it('renders the contact email link', async () => {
|
||||
render(await Footer());
|
||||
const link = screen.getByRole('link', { name: /hello@allmy\.work/i });
|
||||
expect(link).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('email link points to the mailto url', async () => {
|
||||
render(await Footer());
|
||||
const link = screen.getByRole('link', { name: /hello@allmy\.work/i });
|
||||
expect(link).toHaveAttribute('href', 'mailto:hello@allmy.work');
|
||||
});
|
||||
|
||||
it('does not render email link when contacts.email is missing', async () => {
|
||||
it('does not render email link when expand.email is missing', async () => {
|
||||
vi.mocked(getFirstRecord).mockResolvedValue({
|
||||
...mockSettings,
|
||||
expand: {
|
||||
contacts: {
|
||||
...mockSettings.expand.contacts,
|
||||
email: '',
|
||||
expand: {
|
||||
...mockSettings.expand.contacts.expand,
|
||||
email: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -98,7 +118,10 @@ describe('Footer', () => {
|
||||
expand: {
|
||||
contacts: {
|
||||
...mockSettings.expand.contacts,
|
||||
expand: { socials: [] },
|
||||
expand: {
|
||||
...mockSettings.expand.contacts.expand,
|
||||
socials: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user