diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1f14a5d2f..65ad2d34e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- `ActionItemAddress` component
+- `IconUpdate` component
+
+### Changed
+
+- `Link` component to include description
## [0.2.13] - 2023-08-31
diff --git a/src/components/icons/interface/icon_update.tsx b/src/components/icons/interface/icon_update.tsx
new file mode 100644
index 000000000..be3a6149c
--- /dev/null
+++ b/src/components/icons/interface/icon_update.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import { type IconType } from '..';
+
+export const IconUpdate: IconType = ({ height = 16, width = 16, ...props }) => {
+ return (
+
+ );
+};
diff --git a/src/components/icons/interface/index.ts b/src/components/icons/interface/index.ts
index d2f540c97..11b7b9914 100644
--- a/src/components/icons/interface/index.ts
+++ b/src/components/icons/interface/index.ts
@@ -43,6 +43,7 @@ export { IconStorage } from './icon_storage';
export { IconSuccess } from './icon_success';
export { IconSwitch } from './icon_switch';
export { IconTurnOff } from './icon_turn_off';
+export { IconUpdate } from './icon_update';
export { IconWarning } from './icon_warning';
export { IconWithdraw } from './icon_withdraw';
export { IconRadioCancel } from './radio';
diff --git a/src/components/link/link.stories.tsx b/src/components/link/link.stories.tsx
index 2a3a35b27..120e0eb19 100644
--- a/src/components/link/link.stories.tsx
+++ b/src/components/link/link.stories.tsx
@@ -1,35 +1,46 @@
-import { type Meta, type Story } from '@storybook/react';
+import { type Meta, type StoryObj } from '@storybook/react';
import React from 'react';
-import { IconChevronDown } from '../icons';
-import { Link, type LinkProps } from './link';
-export default {
- title: 'Components/Link',
+import { IconLinkExternal } from '../icons';
+import { LINK_VARIANTS, Link } from './link';
+
+const meta: Meta = {
component: Link,
-} as Meta;
+ title: 'Components/Link',
+ tags: ['autodocs'],
+ argTypes: {
+ label: { control: { type: 'text' } },
+ description: { control: { type: 'text' } },
+ type: {
+ options: LINK_VARIANTS,
+ control: { type: 'select' },
+ defaultValue: 'primary',
+ },
+ },
+};
-const Template: Story = (args) => ;
+export default meta;
-export const Default = Template.bind({});
-Default.args = {
- label: 'Link text',
- href: 'https://aragon.org/',
- type: 'primary',
-};
+type Story = StoryObj;
-export const IconRight = Template.bind({});
-IconRight.args = {
- iconRight: ,
- label: 'Link text',
- href: 'https://aragon.org/',
- type: 'secondary',
+export const Primary: Story = {
+ render: (args) => ,
+ args: {
+ label: 'Aragon',
+ description: 'Association Website',
+ href: 'https://aragon.org/',
+ type: 'primary',
+ iconRight: ,
+ },
};
-export const IconLeft = Template.bind({});
-IconLeft.args = {
- iconLeft: ,
- label: 'Link text',
- href: 'https://aragon.org/',
- disabled: true,
- type: 'neutral',
+export const Neutral: Story = {
+ render: (args) => ,
+ args: {
+ label: 'Aragon',
+ description: 'Association Website',
+ href: 'https://aragon.org/',
+ type: 'neutral',
+ iconRight: ,
+ },
};
diff --git a/src/components/link/link.test.tsx b/src/components/link/link.test.tsx
index 3fac52476..cb96136b4 100644
--- a/src/components/link/link.test.tsx
+++ b/src/components/link/link.test.tsx
@@ -1,16 +1,15 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
-import { Link } from './link';
+import { Link, type LinkProps } from './link';
describe('Link', () => {
- // eslint-disable-next-line
- function setup(args: any) {
+ function setup(args: LinkProps = {} as LinkProps) {
render();
return screen.getByTestId('link');
}
test('should render without crashing', () => {
- const element = setup({});
+ const element = setup();
expect(element).toBeInTheDocument;
});
});
diff --git a/src/components/link/link.tsx b/src/components/link/link.tsx
index f4525a52f..2bb932b43 100644
--- a/src/components/link/link.tsx
+++ b/src/components/link/link.tsx
@@ -5,78 +5,86 @@ import { type IconType } from '../icons';
export type LinkProps = React.AnchorHTMLAttributes & {
disabled?: boolean;
- /** whether link should open new tab to external location */
+ /** Indicates whether the link should open in a new tab */
external?: boolean;
iconRight?: React.FunctionComponentElement;
- iconLeft?: React.FunctionComponentElement;
- /** optional label for the link, defaults to the href if value not provided */
- label?: string;
- /** link variants */
- type?: 'primary' | 'secondary' | 'neutral';
+ /** Label for the link */
+ label: string;
+ /** Optional description */
+ description?: string;
+ /** Variants for link appearance */
+ type?: LinkType;
};
-/** Default link component */
-export const Link: React.FC = ({
- disabled = false,
- external = true,
- type = 'primary',
- iconLeft,
- iconRight,
- label,
- href,
- ...props
-}) => {
- return (
-
- {iconLeft && {iconLeft}
}
-
- {!iconLeft && iconRight && {iconRight}
}
-
- );
-};
+export const LINK_VARIANTS = ['primary', 'neutral'] as const;
+type LinkType = (typeof LINK_VARIANTS)[number];
+
+/**
+ * The Link component creates a styled anchor element with optional icon and description.
+ *
+ * @param {LinkProps} props - The properties of the link.
+ */
+export const Link = React.forwardRef(
+ ({ disabled = false, external = true, type = 'primary', iconRight, description, label, href, ...props }, ref) => {
+ return (
+
+
+
+ {iconRight &&
{iconRight}
}
+
+ {description && {description}}
+
+ );
+ },
+);
+
+Link.displayName = 'Link';
type StyledLinkProps = Pick & {
type: NonNullable;
};
const StyledLink = styled.a.attrs(({ disabled, type }: StyledLinkProps) => {
- let className = 'inline-flex items-center space-x-1.5 max-w-full rounded cursor-pointer ';
-
+ let className = 'inline-flex flex-col gap-y-0.25 tablet:gap-y-0.5 max-w-full rounded cursor-pointer ';
className += variants[type];
-
className += disabled ? disabledColors[type] : defaultColors[type];
return { className };
})`
- outline: 0; // forcefully setting to remove default Chrome black outline
+ outline: 0; // Remove default Chrome black outline
`;
const Label = styled.span.attrs({
- className: 'font-bold truncate',
+ className: 'ft-text-base font-semibold truncate',
+})``;
+
+const Description = styled.p.attrs({
+ className: 'ft-text-sm text-ui-600 truncate',
})``;
const variants = {
- primary: 'hover:text-primary-700 active:text-primary-800 focus-visible:ring-2 focus-visible:ring-primary-500 ',
- secondary: 'hover:text-primary-100 active:text-primary-900 focus-visible:ring-2 focus-visible:ring-ui-0 ',
- neutral: 'hover:text-primary-700 active:text-primary-800 focus-visible:ring-2 focus-visible:ring-primary-500 ',
+ primary: `hover:text-primary-600 active:text-primary-800
+ focus-visible:ring focus-visible:ring-primary-200 focus-visible:bg-ui-50 `,
+
+ neutral: `hover:text-ui-800 active:text-ui-800
+ focus-visible:ring focus-visible:ring-primary-200 focus-visible:bg-ui-50 `,
};
const disabledColors = {
primary: 'text-ui-300 pointer-events-none ',
- secondary: 'text-primary-300 pointer-events-none ',
neutral: 'text-ui-300 pointer-events-none ',
};
const defaultColors = {
- primary: 'text-primary-500 ',
- secondary: 'text-ui-0 ',
- neutral: 'text-ui-500 ',
+ primary: 'text-primary-400 ',
+ neutral: 'text-ui-600 ',
};