From 09451bfe4fd948d040cb54ec7cab5d0679322cf6 Mon Sep 17 00:00:00 2001 From: Richard Dinh <1038306+flacoman91@users.noreply.github.com> Date: Sat, 7 Mar 2026 19:45:07 -0800 Subject: [PATCH 1/4] adding appearance to the Link component to match button and DS example --- src/components/Link/link.stories.tsx | 46 ++++++++++++++++++++++++++-- src/components/Link/link.tsx | 8 ++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/components/Link/link.stories.tsx b/src/components/Link/link.stories.tsx index f819e26ac6..dc2e7fadf0 100644 --- a/src/components/Link/link.stories.tsx +++ b/src/components/Link/link.stories.tsx @@ -7,6 +7,21 @@ const meta: Meta = { title: 'Components (Verified)/Links', tags: ['autodocs'], component: Link, + argTypes: { + href: { control: 'text' }, + label: { control: 'text' }, + type: { control: 'select' }, + isButton: { control: 'boolean' }, + isJump: { control: 'boolean' }, + isRouterLink: { control: 'boolean' }, + appearance: { + control: 'select', + options: ['primary', 'secondary', 'warning'], + }, + iconLeft: { control: 'text' }, + iconRight: { control: 'text' }, + children: { control: 'text' }, + }, }; export default meta; @@ -15,8 +30,35 @@ type Story = StoryObj; const DefaultArguments = { args: { - href: '#', - children: 'Link Text', + href: '/#', + label: 'Link Text', + type: 'default', + isButton: false, + isJump: false, + isRouterLink: false, + appearance: 'primary', + iconLeft: undefined, + iconRight: undefined, + children: undefined, + }, +}; + +export const Configurable: Story = { + args: { + ...DefaultArguments.args, + }, + render: (arguments_) => + arguments_.isRouterLink ? ( + + + + ) : ( + + ), + play: async ({ canvasElement, args }) => { + const canvas = within(canvasElement); + const link = canvas.getByRole('link'); + await expect(link).toHaveAttribute('href', args.href); }, }; diff --git a/src/components/Link/link.tsx b/src/components/Link/link.tsx index fa86b4f17f..9b4905adfd 100644 --- a/src/components/Link/link.tsx +++ b/src/components/Link/link.tsx @@ -13,6 +13,10 @@ export interface LinkProperties extends HTMLProps { * Whether the link should be rendered as a button. */ isButton?: boolean; + /** + * What is the link's appearance when rendered as a button? + */ + appearance?: 'primary' | 'secondary' | 'warning'; /** * Any children to render within the link. Allows you to wrap any node with anchor tag */ @@ -56,6 +60,7 @@ export interface LinkProperties extends HTMLProps { */ export default function Link({ isButton = false, + appearance, children, href, iconLeft, @@ -75,7 +80,8 @@ export default function Link({ const cname = classnames(others.className, { 'a-btn': isButton || type === 'destructive', 'a-btn--link': type === 'destructive', - 'a-btn--warning': type === 'destructive', + 'a-btn--warning': type === 'destructive' || (isButton && appearance === 'warning'), + 'a-btn--secondary': isButton && appearance === 'secondary', 'a-link--jump': isJump, 'a-link': shouldUseLinkStyles, }); From 6c6a04c4d621bcb4a188ace5624c5de2d49a6e11 Mon Sep 17 00:00:00 2001 From: Richard Dinh <1038306+flacoman91@users.noreply.github.com> Date: Sat, 7 Mar 2026 19:46:23 -0800 Subject: [PATCH 2/4] update Link test --- src/components/Link/link.test.tsx | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/components/Link/link.test.tsx b/src/components/Link/link.test.tsx index dacb77da9e..bdfd1fe60e 100644 --- a/src/components/Link/link.test.tsx +++ b/src/components/Link/link.test.tsx @@ -73,6 +73,29 @@ describe('', () => { expect(await screen.findByTestId('link-icon-right')).toBeInTheDocument(); }); + it('Option: appearance - it applies button appearance classes when isButton', () => { + render( + , + ); + const link = screen.getByTestId(testId); + expect(link).toHaveClass('a-btn'); + expect(link).toHaveClass('a-btn--secondary'); + }); + + it('Option: appearance - it applies warning class when isButton', () => { + render(); + const link = screen.getByTestId(testId); + expect(link).toHaveClass('a-btn'); + expect(link).toHaveClass('a-btn--warning'); + }); + + it('Option: appearance - it does not apply button appearance classes without isButton', () => { + render(); + const link = screen.getByTestId(testId); + expect(link).not.toHaveClass('a-btn--secondary'); + expect(link).not.toHaveClass('a-btn--warning'); + }); + it('Other: propagates other attributes', () => { render(); const link = screen.getByTestId(testId); From bcb2c2fbe7e77b5412a19e6dca7fa4f135dc9b7d Mon Sep 17 00:00:00 2001 From: Richard Dinh <1038306+flacoman91@users.noreply.github.com> Date: Thu, 12 Mar 2026 08:09:10 -0700 Subject: [PATCH 3/4] update Link as button examples and options --- src/components/Link/link.stories.tsx | 6 +++--- src/components/Link/link.test.tsx | 8 ++++---- src/components/Link/link.tsx | 16 ++++++---------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/components/Link/link.stories.tsx b/src/components/Link/link.stories.tsx index dc2e7fadf0..39a9c8fc2b 100644 --- a/src/components/Link/link.stories.tsx +++ b/src/components/Link/link.stories.tsx @@ -10,7 +10,6 @@ const meta: Meta = { argTypes: { href: { control: 'text' }, label: { control: 'text' }, - type: { control: 'select' }, isButton: { control: 'boolean' }, isJump: { control: 'boolean' }, isRouterLink: { control: 'boolean' }, @@ -32,7 +31,6 @@ const DefaultArguments = { args: { href: '/#', label: 'Link Text', - type: 'default', isButton: false, isJump: false, isRouterLink: false, @@ -159,7 +157,9 @@ export const Destructive: Story = { args: { ...DefaultArguments.args, }, - render: () => , + render: () => ( + + ), }; export const LinkWithReactRouterLink: Story = { diff --git a/src/components/Link/link.test.tsx b/src/components/Link/link.test.tsx index bdfd1fe60e..a7f4944152 100644 --- a/src/components/Link/link.test.tsx +++ b/src/components/Link/link.test.tsx @@ -11,14 +11,14 @@ describe('', () => { }; const testId = linkBaseProperties['data-testid']; - it('Type: "default"', () => { + it('Appearance: "primary" (default)', () => { render(); const link = screen.getByTestId(testId); expect(link).toHaveAttribute('href', '/foo/bar'); }); - it('Type: "destructive"', () => { - render(); + it('Appearance: "warning" (destructive)', () => { + render(); const link = screen.getByTestId(testId); expect(link).toHaveClass('a-btn a-btn--link a-btn--warning'); }); @@ -89,7 +89,7 @@ describe('', () => { expect(link).toHaveClass('a-btn--warning'); }); - it('Option: appearance - it does not apply button appearance classes without isButton', () => { + it('Option: appearance - it does not apply secondary class without isButton', () => { render(); const link = screen.getByTestId(testId); expect(link).not.toHaveClass('a-btn--secondary'); diff --git a/src/components/Link/link.tsx b/src/components/Link/link.tsx index 9b4905adfd..1714ed8540 100644 --- a/src/components/Link/link.tsx +++ b/src/components/Link/link.tsx @@ -14,9 +14,9 @@ export interface LinkProperties extends HTMLProps { */ isButton?: boolean; /** - * What is the link's appearance when rendered as a button? + * What is the link's appearance? */ - appearance?: 'primary' | 'secondary' | 'warning'; + appearance?: 'primary' | 'secondary' | 'destructive' | 'warning'; /** * Any children to render within the link. Allows you to wrap any node with anchor tag */ @@ -46,10 +46,6 @@ export interface LinkProperties extends HTMLProps { */ label?: string; ref?: Ref; - /** - * What type of link should be rendered - */ - type?: 'default' | 'destructive'; } /** @@ -68,7 +64,6 @@ export default function Link({ isJump = false, isRouterLink = false, label, - type = 'default', ...others }: LinkProperties): JSXElement { const hasLeftIcon = Boolean(iconLeft); @@ -77,10 +72,11 @@ export default function Link({ const shouldUseLinkStyles = !isButton && (hasIcons || isJump); const shouldWrapLabel = isButton || shouldUseLinkStyles; const labelNode = shouldWrapLabel ? {label} : label; + const isDestructive = ['destructive', 'warning'].includes(appearance); const cname = classnames(others.className, { - 'a-btn': isButton || type === 'destructive', - 'a-btn--link': type === 'destructive', - 'a-btn--warning': type === 'destructive' || (isButton && appearance === 'warning'), + 'a-btn': isButton || isDestructive, + 'a-btn--link': isDestructive && !isButton, + 'a-btn--warning': isDestructive, 'a-btn--secondary': isButton && appearance === 'secondary', 'a-link--jump': isJump, 'a-link': shouldUseLinkStyles, From 6e60782549150386058d51d209ea246eeb4726ec Mon Sep 17 00:00:00 2001 From: Richard Dinh <1038306+flacoman91@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:50:38 -0700 Subject: [PATCH 4/4] add destructive option to storybook --- src/components/Link/link.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Link/link.stories.tsx b/src/components/Link/link.stories.tsx index 39a9c8fc2b..a553f11dd0 100644 --- a/src/components/Link/link.stories.tsx +++ b/src/components/Link/link.stories.tsx @@ -15,7 +15,7 @@ const meta: Meta = { isRouterLink: { control: 'boolean' }, appearance: { control: 'select', - options: ['primary', 'secondary', 'warning'], + options: ['primary', 'secondary', 'warning', 'destructive'], }, iconLeft: { control: 'text' }, iconRight: { control: 'text' },