From 2d9fcde7122f8bfac46e39f472b0975a99cac3a7 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 17:59:49 +0000 Subject: [PATCH 1/5] feat(components): add underline prop to Link component Co-Authored-By: Harsh Sadhvani --- packages/components/__tests__/Link.spec.tsx | 14 ++++++++++++++ packages/components/src/Link.tsx | 10 ++++++++-- packages/components/src/styles/Link.module.css | 4 ++++ packages/components/stories/Link.stories.tsx | 8 ++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/components/__tests__/Link.spec.tsx b/packages/components/__tests__/Link.spec.tsx index a5f47e8ee..3beedeb27 100644 --- a/packages/components/__tests__/Link.spec.tsx +++ b/packages/components/__tests__/Link.spec.tsx @@ -30,4 +30,18 @@ describe('Link', () => { expect(screen.getByRole('link')).toBeVisible(); expect(screen.getByRole('link')).toHaveAttribute('href', 'https://www.test.com'); }); + + it('renders with underline', () => { + const navigate = vi.fn(); + render( + + + + Link + + + , + ); + expect(screen.getByRole('link')).toBeVisible(); + }); }); diff --git a/packages/components/src/Link.tsx b/packages/components/src/Link.tsx index 481b5c37e..d4b309698 100644 --- a/packages/components/src/Link.tsx +++ b/packages/components/src/Link.tsx @@ -16,14 +16,20 @@ const linkStyles = cva(styles.base, { default: styles.default, subtle: styles.subtle, }, + underline: { + true: styles.underline, + }, }, defaultVariants: { variant: 'default', + underline: false, }, }); interface LinkProps extends AriaLinkProps, VariantProps, DOMProps { ref?: Ref; + /** Whether the link should always be underlined, not just on hover. */ + underline?: boolean; } const LinkContext = createContext>(null); @@ -35,14 +41,14 @@ const LinkContext = createContext>(nu */ const Link = ({ ref, ...props }: LinkProps) => { [props, ref] = useLPContextProps(props, ref, LinkContext); - const { variant = 'default' } = props; + const { variant = 'default', underline = false } = props; return ( - linkStyles({ ...renderProps, variant, className }), + linkStyles({ ...renderProps, variant, underline, className }), )} /> ); diff --git a/packages/components/src/styles/Link.module.css b/packages/components/src/styles/Link.module.css index 1f7bcc9b3..c0ab7b7d7 100644 --- a/packages/components/src/styles/Link.module.css +++ b/packages/components/src/styles/Link.module.css @@ -41,3 +41,7 @@ composes: link; color: var(--lp-color-text-ui-secondary); } + +.underline { + text-decoration: underline; +} diff --git a/packages/components/stories/Link.stories.tsx b/packages/components/stories/Link.stories.tsx index 4d827bfb7..ef273ac65 100644 --- a/packages/components/stories/Link.stories.tsx +++ b/packages/components/stories/Link.stories.tsx @@ -35,6 +35,14 @@ export const Subtle: Story = { }, }; +export const Underline: Story = { + args: { + children: 'Link', + href: '/test', + underline: true, + }, +}; + export const States: Story = { render: (args) => { const styles = { width: 'fit-content' }; From 970c31ebeb43a446fe11a4f40fb6d54d0446d8bc Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 18:01:12 +0000 Subject: [PATCH 2/5] add changeset Co-Authored-By: Harsh Sadhvani --- .changeset/link-underline-prop.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/link-underline-prop.md diff --git a/.changeset/link-underline-prop.md b/.changeset/link-underline-prop.md new file mode 100644 index 000000000..845c31477 --- /dev/null +++ b/.changeset/link-underline-prop.md @@ -0,0 +1,5 @@ +--- +"@launchpad-ui/components": minor +--- + +Add `underline` prop to Link component for persistent underline styling From fdb49947b407a66cdf942f81615dde930b64fd0b Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 18:17:04 +0000 Subject: [PATCH 3/5] rename underline prop to evident Co-Authored-By: Harsh Sadhvani --- .changeset/link-underline-prop.md | 2 +- packages/components/__tests__/Link.spec.tsx | 4 ++-- packages/components/src/Link.tsx | 12 ++++++------ packages/components/src/styles/Link.module.css | 2 +- packages/components/stories/Link.stories.tsx | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.changeset/link-underline-prop.md b/.changeset/link-underline-prop.md index 845c31477..12e9f53df 100644 --- a/.changeset/link-underline-prop.md +++ b/.changeset/link-underline-prop.md @@ -2,4 +2,4 @@ "@launchpad-ui/components": minor --- -Add `underline` prop to Link component for persistent underline styling +Add `evident` prop to Link component for persistent underline styling diff --git a/packages/components/__tests__/Link.spec.tsx b/packages/components/__tests__/Link.spec.tsx index 3beedeb27..36b769209 100644 --- a/packages/components/__tests__/Link.spec.tsx +++ b/packages/components/__tests__/Link.spec.tsx @@ -31,12 +31,12 @@ describe('Link', () => { expect(screen.getByRole('link')).toHaveAttribute('href', 'https://www.test.com'); }); - it('renders with underline', () => { + it('renders with evident', () => { const navigate = vi.fn(); render( - + Link diff --git a/packages/components/src/Link.tsx b/packages/components/src/Link.tsx index d4b309698..999ee04e0 100644 --- a/packages/components/src/Link.tsx +++ b/packages/components/src/Link.tsx @@ -16,20 +16,20 @@ const linkStyles = cva(styles.base, { default: styles.default, subtle: styles.subtle, }, - underline: { - true: styles.underline, + evident: { + true: styles.evident, }, }, defaultVariants: { variant: 'default', - underline: false, + evident: false, }, }); interface LinkProps extends AriaLinkProps, VariantProps, DOMProps { ref?: Ref; /** Whether the link should always be underlined, not just on hover. */ - underline?: boolean; + evident?: boolean; } const LinkContext = createContext>(null); @@ -41,14 +41,14 @@ const LinkContext = createContext>(nu */ const Link = ({ ref, ...props }: LinkProps) => { [props, ref] = useLPContextProps(props, ref, LinkContext); - const { variant = 'default', underline = false } = props; + const { variant = 'default', evident = false } = props; return ( - linkStyles({ ...renderProps, variant, underline, className }), + linkStyles({ ...renderProps, variant, evident, className }), )} /> ); diff --git a/packages/components/src/styles/Link.module.css b/packages/components/src/styles/Link.module.css index c0ab7b7d7..72f16f97c 100644 --- a/packages/components/src/styles/Link.module.css +++ b/packages/components/src/styles/Link.module.css @@ -42,6 +42,6 @@ color: var(--lp-color-text-ui-secondary); } -.underline { +.evident { text-decoration: underline; } diff --git a/packages/components/stories/Link.stories.tsx b/packages/components/stories/Link.stories.tsx index ef273ac65..06faf8bb6 100644 --- a/packages/components/stories/Link.stories.tsx +++ b/packages/components/stories/Link.stories.tsx @@ -35,11 +35,11 @@ export const Subtle: Story = { }, }; -export const Underline: Story = { +export const Evident: Story = { args: { children: 'Link', href: '/test', - underline: true, + evident: true, }, }; From f2014bf7273df542166b3d97a49e806f67ec7739 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 19:00:20 +0000 Subject: [PATCH 4/5] convert to variant='underline' instead of boolean prop Co-Authored-By: Harsh Sadhvani --- .changeset/link-underline-prop.md | 2 +- packages/components/__tests__/Link.spec.tsx | 4 ++-- packages/components/src/Link.tsx | 11 +++-------- packages/components/src/styles/Link.module.css | 4 +++- packages/components/stories/Link.stories.tsx | 4 ++-- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/.changeset/link-underline-prop.md b/.changeset/link-underline-prop.md index 12e9f53df..ab1821708 100644 --- a/.changeset/link-underline-prop.md +++ b/.changeset/link-underline-prop.md @@ -2,4 +2,4 @@ "@launchpad-ui/components": minor --- -Add `evident` prop to Link component for persistent underline styling +Add `underline` variant to Link component for persistent underline styling diff --git a/packages/components/__tests__/Link.spec.tsx b/packages/components/__tests__/Link.spec.tsx index 36b769209..08807ed9f 100644 --- a/packages/components/__tests__/Link.spec.tsx +++ b/packages/components/__tests__/Link.spec.tsx @@ -31,12 +31,12 @@ describe('Link', () => { expect(screen.getByRole('link')).toHaveAttribute('href', 'https://www.test.com'); }); - it('renders with evident', () => { + it('renders with underline variant', () => { const navigate = vi.fn(); render( - + Link diff --git a/packages/components/src/Link.tsx b/packages/components/src/Link.tsx index 999ee04e0..e095eb16b 100644 --- a/packages/components/src/Link.tsx +++ b/packages/components/src/Link.tsx @@ -15,21 +15,16 @@ const linkStyles = cva(styles.base, { variant: { default: styles.default, subtle: styles.subtle, - }, - evident: { - true: styles.evident, + underline: styles.underline, }, }, defaultVariants: { variant: 'default', - evident: false, }, }); interface LinkProps extends AriaLinkProps, VariantProps, DOMProps { ref?: Ref; - /** Whether the link should always be underlined, not just on hover. */ - evident?: boolean; } const LinkContext = createContext>(null); @@ -41,14 +36,14 @@ const LinkContext = createContext>(nu */ const Link = ({ ref, ...props }: LinkProps) => { [props, ref] = useLPContextProps(props, ref, LinkContext); - const { variant = 'default', evident = false } = props; + const { variant = 'default' } = props; return ( - linkStyles({ ...renderProps, variant, evident, className }), + linkStyles({ ...renderProps, variant, className }), )} /> ); diff --git a/packages/components/src/styles/Link.module.css b/packages/components/src/styles/Link.module.css index 72f16f97c..555872050 100644 --- a/packages/components/src/styles/Link.module.css +++ b/packages/components/src/styles/Link.module.css @@ -42,6 +42,8 @@ color: var(--lp-color-text-ui-secondary); } -.evident { +.underline { + composes: link; + color: var(--lp-color-text-interactive-base); text-decoration: underline; } diff --git a/packages/components/stories/Link.stories.tsx b/packages/components/stories/Link.stories.tsx index 06faf8bb6..4ca63fb6f 100644 --- a/packages/components/stories/Link.stories.tsx +++ b/packages/components/stories/Link.stories.tsx @@ -35,11 +35,11 @@ export const Subtle: Story = { }, }; -export const Evident: Story = { +export const Underline: Story = { args: { children: 'Link', href: '/test', - evident: true, + variant: 'underline', }, }; From 02d01e81e2feabe4a6d640f10d844997c84b7327 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 19:06:42 +0000 Subject: [PATCH 5/5] switch to MUI-style underline prop (always/hover/none) Co-Authored-By: Harsh Sadhvani --- .changeset/link-underline-prop.md | 2 +- packages/components/__tests__/Link.spec.tsx | 4 ++-- packages/components/src/Link.tsx | 13 ++++++++++--- packages/components/src/styles/Link.module.css | 15 ++++++++++++--- packages/components/stories/Link.stories.tsx | 12 ++++++++++-- 5 files changed, 35 insertions(+), 11 deletions(-) diff --git a/.changeset/link-underline-prop.md b/.changeset/link-underline-prop.md index ab1821708..ff2072e65 100644 --- a/.changeset/link-underline-prop.md +++ b/.changeset/link-underline-prop.md @@ -2,4 +2,4 @@ "@launchpad-ui/components": minor --- -Add `underline` variant to Link component for persistent underline styling +Add `underline` prop to Link component with `'always' | 'hover' | 'none'` values diff --git a/packages/components/__tests__/Link.spec.tsx b/packages/components/__tests__/Link.spec.tsx index 08807ed9f..f3f18a3f3 100644 --- a/packages/components/__tests__/Link.spec.tsx +++ b/packages/components/__tests__/Link.spec.tsx @@ -31,12 +31,12 @@ describe('Link', () => { expect(screen.getByRole('link')).toHaveAttribute('href', 'https://www.test.com'); }); - it('renders with underline variant', () => { + it('renders with underline="always"', () => { const navigate = vi.fn(); render( - + Link diff --git a/packages/components/src/Link.tsx b/packages/components/src/Link.tsx index e095eb16b..7e775c9c2 100644 --- a/packages/components/src/Link.tsx +++ b/packages/components/src/Link.tsx @@ -15,16 +15,23 @@ const linkStyles = cva(styles.base, { variant: { default: styles.default, subtle: styles.subtle, - underline: styles.underline, + }, + underline: { + always: styles.underlineAlways, + hover: styles.underlineHover, + none: styles.underlineNone, }, }, defaultVariants: { variant: 'default', + underline: 'hover', }, }); interface LinkProps extends AriaLinkProps, VariantProps, DOMProps { ref?: Ref; + /** Controls when the link is underlined. */ + underline?: 'always' | 'hover' | 'none'; } const LinkContext = createContext>(null); @@ -36,14 +43,14 @@ const LinkContext = createContext>(nu */ const Link = ({ ref, ...props }: LinkProps) => { [props, ref] = useLPContextProps(props, ref, LinkContext); - const { variant = 'default' } = props; + const { variant = 'default', underline = 'hover' } = props; return ( - linkStyles({ ...renderProps, variant, className }), + linkStyles({ ...renderProps, variant, underline, className }), )} /> ); diff --git a/packages/components/src/styles/Link.module.css b/packages/components/src/styles/Link.module.css index 555872050..fe94995df 100644 --- a/packages/components/src/styles/Link.module.css +++ b/packages/components/src/styles/Link.module.css @@ -42,8 +42,17 @@ color: var(--lp-color-text-ui-secondary); } -.underline { - composes: link; - color: var(--lp-color-text-interactive-base); +.underlineAlways { text-decoration: underline; } + +.underlineHover { +} + +.underlineNone { + text-decoration: none; + + &[data-hovered] { + text-decoration: none; + } +} diff --git a/packages/components/stories/Link.stories.tsx b/packages/components/stories/Link.stories.tsx index 4ca63fb6f..166cbdcc4 100644 --- a/packages/components/stories/Link.stories.tsx +++ b/packages/components/stories/Link.stories.tsx @@ -35,11 +35,19 @@ export const Subtle: Story = { }, }; -export const Underline: Story = { +export const UnderlineAlways: Story = { args: { children: 'Link', href: '/test', - variant: 'underline', + underline: 'always', + }, +}; + +export const UnderlineNone: Story = { + args: { + children: 'Link', + href: '/test', + underline: 'none', }, };