Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 5 additions & 14 deletions src/components/Hero.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Box, Typography, Grid, useMediaQuery, Link } from '@mui/material';
import { Box, Typography, Grid, useMediaQuery } from '@mui/material';
import Image from 'next/image';
import React from 'react';

import { GradientBorderDivider } from '@components';
import { GradientBorderDivider, LinkButton } from '@components';

import theme from '../theme';
import { LandingPageResponse } from '../utils/types';
Expand Down Expand Up @@ -60,21 +60,12 @@ export const Hero: React.FC<HeroProps> = ({ title, subtitle, images }) => {
>
{subtitle}
</Typography>
<Link
<LinkButton
href="https://join.slack.com/t/womencodingcommunity/shared_invite/zt-2hpjwpx7l-rgceYBIWp6pCiwc0hVsX8A"
target="_blank"
rel="noopener noreferrer"
sx={{
fontSize: '1.25rem',
textDecoration: 'underline',
color: 'theme.palette.primary.main',
'&:hover': {
textDecoration: 'none',
},
}}
outlined
>
Join our Slack
</Link>
</LinkButton>
</Box>
</Grid>
<Grid item xs={12} sm={7} style={{ padding: 0, margin: 0 }}>
Expand Down
80 changes: 64 additions & 16 deletions src/components/LinkButton.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,71 @@
import { Button } from '@mui/material';
import { Button, type SxProps, type Theme } from '@mui/material';
import Link from 'next/link';
import React from 'react';

type LinkButtonProps = {
href: string;
reversed?: boolean;
outlined?: boolean;
small?: boolean;
children: React.ReactNode;
};

const pillRadius = '100px';

export const LinkButton = ({
href,
reversed,
outlined,
small,
children,
}: LinkButtonProps) => {
const isExternal = href.startsWith('https');

const padding = small ? '7px 16px' : '10px 32px';

if (outlined) {
const outlinedPadding = small ? '7px 16px' : '10px 24px';
const outlinedSx: SxProps<Theme> = (theme) => ({
...(small
? theme.typography.outlineButtonSmall
: theme.typography.outlineButton),
borderRadius: pillRadius,
padding: outlinedPadding,
minHeight: small ? undefined : '40px',
borderColor: theme.palette.custom.outline,
color: 'primary.main',
boxShadow: 'none',
'&:hover': {
borderColor: theme.palette.custom.outline,
backgroundColor: 'primary.light',
boxShadow: 'none',
},
});

if (isExternal) {
return (
<Button
component="a"
href={href}
target="_blank"
rel="noopener noreferrer"
variant="outlined"
sx={outlinedSx}
>
{children}
</Button>
);
}

return (
<Link href={href} passHref legacyBehavior>
<Button component="a" variant="outlined" sx={outlinedSx}>
{children}
</Button>
</Link>
);
}

if (isExternal) {
return (
<Button
Expand All @@ -25,15 +74,15 @@ export const LinkButton = ({
target="_blank"
rel="noopener noreferrer"
variant="contained"
sx={{
sx={(theme) => ({
...(small
? theme.typography.linkButtonContainedSmall
: theme.typography.linkButtonContained),
backgroundColor: reversed ? '#fff' : 'primary.main',
color: reversed ? 'primary.main' : '#fff',
borderRadius: '100px',
textTransform: 'none',
fontWeight: 600,
fontSize: '1rem',
padding: '10px 32px',
}}
borderRadius: pillRadius,
padding,
})}
>
{children}
</Button>
Expand All @@ -45,16 +94,15 @@ export const LinkButton = ({
<Button
component="a"
variant="contained"
sx={{
sx={(theme) => ({
...(small
? theme.typography.linkButtonContainedSmall
: theme.typography.linkButtonContained),
backgroundColor: reversed ? '#fff' : 'primary.main',
color: reversed ? 'primary.main' : '#fff',
borderRadius: '100px',
textTransform: 'none',
fontWeight: 600,
// width: small ? 'fit-content' : '100%',
fontSize: small ? '0.8rem' : '1rem',
padding: small ? '7px 16px' : '10px 32px',
}}
borderRadius: pillRadius,
padding,
})}
>
{children}
</Button>
Expand Down
19 changes: 18 additions & 1 deletion src/components/__tests__/LinkButton.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { ThemeProvider } from '@mui/material';
import { render, screen } from '@testing-library/react';
import React from 'react';

import theme from 'theme';

import { LinkButton } from '../LinkButton';

const renderWithTheme = (ui: React.ReactElement) =>
render(<ThemeProvider theme={theme}>{ui}</ThemeProvider>);

describe('LinkButton', () => {
it('renders an internal link using Next.js Link', () => {
render(<LinkButton href="/internal">Internal Link</LinkButton>);
renderWithTheme(<LinkButton href="/internal">Internal Link</LinkButton>);
const button = screen.getByRole('link', { name: /internal link/i });
expect(button).toBeInTheDocument();
expect(button.closest('a')).toHaveAttribute('href', '/internal');
Expand All @@ -22,4 +28,15 @@ describe('LinkButton', () => {
expect(button.closest('a')).toHaveAttribute('target', '_blank');
expect(button.closest('a')).toHaveAttribute('rel', 'noopener noreferrer');
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing renders an external link test still calls bare render() — it was not updated alongside the internal test. Since sx now calls theme.typography.linkButtonContained, running without ThemeProvider means those custom variants resolve to undefined. Tests still pass (no style assertions), but this is inconsistent with the fix applied to the internal test.

-    render(<LinkButton href="https://external.com">External Link</LinkButton>);
+    renderWithTheme(<LinkButton href="https://external.com">External Link</LinkButton>);


it('renders an outlined external link', () => {
renderWithTheme(
<LinkButton href="https://external.com" outlined>
Outlined External
</LinkButton>,
);
const link = screen.getByRole('link', { name: /outlined external/i });
expect(link).toBeInTheDocument();
expect(link.closest('a')).toHaveAttribute('href', 'https://external.com');
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The outlined branch has two code paths — external (<a>) and internal (Next.js Link) — but only the external path is tested. A companion test would complete the coverage:

it("renders an outlined internal link", () => {
  renderWithTheme(
    <LinkButton href="/internal" outlined>
      Outlined Internal
    </LinkButton>,
  );
  const link = screen.getByRole("link", { name: /outlined internal/i });
  expect(link).toBeInTheDocument();
  expect(link.closest("a")).toHaveAttribute("href", "/internal");
});

8 changes: 4 additions & 4 deletions src/pages/mentorship/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,11 @@ const FeedbackSection: React.FC<FeedbackSectionProps> = ({
}
variant="outlined"
data-testid="feedback-show-more"
sx={{
sx={(t) => ({
borderRadius: '20px',
border: '1px solid #71787E',
color: '#1A4B66',
}}
border: `1px solid ${t.palette.custom.outline}`,
color: t.palette.custom.linkBlue,
})}
>
{feedbacksDisplayed >= feedbacks.length
? '- Show less'
Expand Down
55 changes: 55 additions & 0 deletions src/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
/* eslint-disable unused-imports/no-unused-vars */

import { createTheme } from '@mui/material/styles';
import type { CSSProperties } from 'react';

const COLORS = {
lightBlue: '#C7E7FF',
lightOrange: '#FFDEA6',
lightPink: '#FFDBD0',
softGray: '#F4F0EF',
linkBlue: '#1A4B66',
outline: '#71787E',
} as const;

declare module '@mui/material/styles' {
Expand Down Expand Up @@ -84,6 +86,7 @@ declare module '@mui/material/styles' {
lightOrange: string;
studyGroupCardColors: string[];
linkBlue: string;
outline: string;
};
}
interface PaletteOptions {
Expand All @@ -93,8 +96,32 @@ declare module '@mui/material/styles' {
lightOrange?: string;
studyGroupCardColors?: string[];
linkBlue?: string;
outline?: string;
};
}

interface TypographyVariants {
outlineButton: CSSProperties;
outlineButtonSmall: CSSProperties;
linkButtonContained: CSSProperties;
linkButtonContainedSmall: CSSProperties;
}

interface TypographyVariantsOptions {
outlineButton?: CSSProperties;
outlineButtonSmall?: CSSProperties;
linkButtonContained?: CSSProperties;
linkButtonContainedSmall?: CSSProperties;
}
}

declare module '@mui/material/Typography' {
interface TypographyPropsVariantOverrides {
outlineButton: true;
outlineButtonSmall: true;
linkButtonContained: true;
linkButtonContainedSmall: true;
}
}

const theme = createTheme({
Expand Down Expand Up @@ -204,6 +231,33 @@ const theme = createTheme({
lineHeight: 1.2,
},

outlineButton: {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
fontWeight: 500,
fontSize: '0.875rem',
lineHeight: '20px',
letterSpacing: '0.1px',
textTransform: 'none',
},
outlineButtonSmall: {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
fontWeight: 500,
fontSize: '0.8rem',
lineHeight: '20px',
letterSpacing: '0.1px',
textTransform: 'none',
},
linkButtonContained: {
fontWeight: 600,
fontSize: '1rem',
textTransform: 'none',
},
linkButtonContainedSmall: {
fontWeight: 600,
fontSize: '0.8rem',
textTransform: 'none',
},

fontWeightBold: 600,
fontWeightMedium: 400,
},
Expand All @@ -228,6 +282,7 @@ const theme = createTheme({
COLORS.softGray,
],
linkBlue: COLORS.linkBlue,
outline: COLORS.outline,
},
text: {
primary: '#1b1919',
Expand Down
Loading