Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added external link icon #4757

Closed
wants to merge 12 commits into from
41 changes: 38 additions & 3 deletions docs/src/pages/[platform]/components/link/LinkPropControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import {
export interface LinkPropControlsProps extends LinkProps {
setColor: (value: React.SetStateAction<LinkProps['color']>) => void;
setIsExternal: (value: React.SetStateAction<LinkProps['isExternal']>) => void;
setHideIcon: (value: React.SetStateAction<LinkProps['hideIcon']>) => void;
setLinkIconPosition: (
value: React.SetStateAction<LinkProps['linkIconPosition']>
) => void;
setTextDecoration: (
value: React.SetStateAction<LinkProps['textDecoration']>
) => void;
Expand All @@ -25,6 +29,10 @@ export const LinkPropControls: LinkPropControlsInterface = ({
setColor,
isExternal,
setIsExternal,
hideIcon,
setHideIcon,
linkIconPosition,
setLinkIconPosition,
textDecoration,
setTextDecoration,
children,
Expand All @@ -49,18 +57,45 @@ export const LinkPropControls: LinkPropControlsInterface = ({
>
<option value="none">none</option>
<option value="underline">underline</option>
<option value="underline overline #FF3028">
underline overline #FF3028
<option value="underline overline #ff3028">
underline overline #ff3028
</option>
<option value="underline dotted">underline dotted</option>
<option value="underline dotted red">underline dotted red</option>
<option value="green wavy underline">green wavy underline</option>
</SelectField>
<SwitchField
checked={isExternal}
onChange={(e) => setIsExternal(e.target.checked)}
onChange={(e) => {
setIsExternal(e.target.checked);
setHideIcon(false);
setLinkIconPosition('right');
}}
label="isExternal"
/>

{isExternal ? (
<>
<SwitchField
checked={hideIcon}
onChange={(e) => setHideIcon(e.target.checked)}
label="hideIcon"
/>

{!hideIcon ? (
<SelectField
value={linkIconPosition as string}
onChange={(event) =>
setLinkIconPosition(event.target.value as string)
}
label="linkIconPosition"
>
<option value="left">left</option>
<option value="right">right</option>
</SelectField>
) : null}
</>
) : null}
</Flex>
);
};
13 changes: 11 additions & 2 deletions docs/src/pages/[platform]/components/link/demo.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as React from 'react';
import { Link, LinkProps } from '@aws-amplify/ui-react';

import { Demo } from '@/components/Demo';
import { LinkPropControls } from './LinkPropControls';
import { useLinkProps } from './useLinkProps';
Expand All @@ -15,15 +14,23 @@ const propsToCode = (props: LinkProps) => {
? `\n textDecoration="${props.textDecoration}"`
: '') +
(props.isExternal ? `\n isExternal={${props.isExternal}}` : '') +
(props.isExternal && props.hideIcon
? `\n hideIcon={${props.hideIcon}}`
: '') +
(props.isExternal && !props.hideIcon && props.linkIconPosition !== 'right'
? `\n linkIconPosition="${props.linkIconPosition}"`
: '') +
'\n>' +
`\n ${props.children}\n</Link>`
);
};

const defaultLinkProps = {
isExternal: false,
color: '#007EB9',
hideIcon: false,
color: '#007eb9',
textDecoration: 'none',
linkIconPosition: 'right',
children: 'My Demo Link',
};

Expand All @@ -41,7 +48,9 @@ export const LinkDemo = () => {
href="https://ui.docs.amplify.aws/react/components/link"
color={linkProps.color}
isExternal={linkProps.isExternal}
hideIcon={linkProps.hideIcon}
textDecoration={linkProps.textDecoration}
linkIconPosition={linkProps.linkIconPosition}
>
{linkProps.children}
</Link>
Expand Down
19 changes: 17 additions & 2 deletions docs/src/pages/[platform]/components/link/useLinkProps.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Link, LinkProps } from '@aws-amplify/ui-react';
import * as React from 'react';
import { demoState } from '@/utils/demoState';

import { LinkPropControlsProps } from './LinkPropControls';

interface UseLinkProps {
Expand All @@ -12,6 +11,12 @@ export const useLinkProps: UseLinkProps = (initialValues) => {
const [isExternal, setIsExternal] = React.useState<LinkProps['isExternal']>(
initialValues.isExternal
);
const [hideIcon, setHideIcon] = React.useState<LinkProps['hideIcon']>(
initialValues.hideIcon
);
const [linkIconPosition, setLinkIconPosition] = React.useState<
LinkProps['linkIconPosition']
>(initialValues.linkIconPosition);
const [color, setColor] = React.useState<LinkProps['color']>(
initialValues.color
);
Expand All @@ -25,16 +30,22 @@ export const useLinkProps: UseLinkProps = (initialValues) => {
React.useEffect(() => {
demoState.set(Link.displayName, {
isExternal,
hideIcon,
linkIconPosition,
color,
textDecoration,
children,
});
}, [isExternal, color, textDecoration, children]);
}, [isExternal, hideIcon, linkIconPosition, color, textDecoration, children]);

return React.useMemo(
() => ({
isExternal,
setIsExternal,
hideIcon,
setHideIcon,
linkIconPosition,
setLinkIconPosition,
color,
setColor,
textDecoration,
Expand All @@ -45,6 +56,10 @@ export const useLinkProps: UseLinkProps = (initialValues) => {
[
isExternal,
setIsExternal,
hideIcon,
setHideIcon,
linkIconPosition,
setLinkIconPosition,
color,
setColor,
textDecoration,
Expand Down
54 changes: 54 additions & 0 deletions packages/react/__tests__/__snapshots__/exports.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1540,6 +1540,15 @@ exports[`primitive catalog should match primitives catalog snapshot 1`] = `
"backgroundImage": {
"type": "string",
},
"badgeContent": {
"type": "string",
},
"badgePosition": {
"type": "string",
},
"badgeVariation": {
"type": "string",
},
"basis": {
"type": "string",
},
Expand Down Expand Up @@ -1619,6 +1628,9 @@ exports[`primitive catalog should match primitives catalog snapshot 1`] = `
"grow": {
"type": "string",
},
"hasBadge": {
"type": "boolean",
},
"height": {
"type": "string",
},
Expand Down Expand Up @@ -6493,6 +6505,9 @@ exports[`primitive catalog should match primitives catalog snapshot 1`] = `
"height": {
"type": "string",
},
"hideIcon": {
"type": "boolean",
},
"href": {
"type": "string",
},
Expand All @@ -6517,6 +6532,9 @@ exports[`primitive catalog should match primitives catalog snapshot 1`] = `
"lineHeight": {
"type": "string",
},
"linkIconPosition": {
"type": "string",
},
"margin": {
"type": "string",
},
Expand Down Expand Up @@ -7237,6 +7255,15 @@ exports[`primitive catalog should match primitives catalog snapshot 1`] = `
"backgroundImage": {
"type": "string",
},
"badgeContent": {
"type": "string",
},
"badgePosition": {
"type": "string",
},
"badgeVariation": {
"type": "string",
},
"basis": {
"type": "string",
},
Expand Down Expand Up @@ -7312,6 +7339,9 @@ exports[`primitive catalog should match primitives catalog snapshot 1`] = `
"grow": {
"type": "string",
},
"hasBadge": {
"type": "boolean",
},
"height": {
"type": "string",
},
Expand Down Expand Up @@ -7526,6 +7556,15 @@ exports[`primitive catalog should match primitives catalog snapshot 1`] = `
"backgroundImage": {
"type": "string",
},
"badgeContent": {
"type": "string",
},
"badgePosition": {
"type": "string",
},
"badgeVariation": {
"type": "string",
},
"basis": {
"type": "string",
},
Expand Down Expand Up @@ -7601,6 +7640,9 @@ exports[`primitive catalog should match primitives catalog snapshot 1`] = `
"grow": {
"type": "string",
},
"hasBadge": {
"type": "boolean",
},
"height": {
"type": "string",
},
Expand Down Expand Up @@ -15315,6 +15357,15 @@ exports[`primitive catalog should match primitives catalog snapshot 1`] = `
"backgroundImage": {
"type": "string",
},
"badgeContent": {
"type": "string",
},
"badgePosition": {
"type": "string",
},
"badgeVariation": {
"type": "string",
},
"basis": {
"type": "string",
},
Expand Down Expand Up @@ -15393,6 +15444,9 @@ exports[`primitive catalog should match primitives catalog snapshot 1`] = `
"grow": {
"type": "string",
},
"hasBadge": {
"type": "boolean",
},
"height": {
"type": "string",
},
Expand Down
26 changes: 22 additions & 4 deletions packages/react/src/primitives/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import * as React from 'react';
import { classNames } from '@aws-amplify/ui';

import { ComponentClassName } from '@aws-amplify/ui';

import { classNames, ComponentClassName } from '@aws-amplify/ui';
import { classNameModifier, classNameModifierByFlag } from '../shared/utils';
import {
BaseButtonProps,
Expand All @@ -17,6 +14,7 @@ import { useFieldset } from '../Fieldset/useFieldset';
import { Flex } from '../Flex';
import { Loader } from '../Loader';
import { View } from '../View';
import { Badge } from '../Badge';

// These variations support colorThemes. 'undefined' accounts for our
// 'default' variation which is not named.
Expand All @@ -34,6 +32,10 @@ const ButtonPrimitive: Primitive<ButtonProps, 'button'> = (
size,
type = 'button',
variation,
hasBadge = false,
badgeContent = '',
badgePosition = 'top-right',
badgeVariation,
...rest
},
ref
Expand Down Expand Up @@ -70,6 +72,16 @@ const ButtonPrimitive: Primitive<ButtonProps, 'button'> = (
className
);

const badgeClasses = classNames(
ComponentClassName.ButtonBadge,
classNameModifier(ComponentClassName.ButtonBadge, badgePosition),
// Add padding to the badge if the button is not a link
classNameModifier(
ComponentClassName.ButtonBadge,
variation === 'link' ? badgePosition : `${badgePosition}--padding`
)
);

return (
<View
ref={ref}
Expand All @@ -79,6 +91,12 @@ const ButtonPrimitive: Primitive<ButtonProps, 'button'> = (
type={type}
{...rest}
>
{hasBadge ? (
<Badge className={badgeClasses} variation={badgeVariation}>
{badgeContent}
</Badge>
) : null}

{isLoading ? (
<Flex as="span" className={ComponentClassName.ButtonLoaderWrapper}>
<Loader size={size} />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as React from 'react';
import { classNames, ComponentClassName } from '@aws-amplify/ui';
import { View } from '../../View';
import { InternalIcon } from './types';

/**
* @internal For internal Amplify UI use only. May be removed in a future release.
*/

export const IconBoxArrowUpRight: InternalIcon = (props) => {
const { className, ...rest } = props;

return (
<View
as="span"
className={classNames(ComponentClassName.Icon, className)}
{...rest}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 16 16"
>
<path
fill="evenodd"
d="M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z"
/>
<path
fill="evenodd"
d="M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z"
/>
</svg>
</View>
);
};
Loading
Loading