Skip to content

Commit

Permalink
fix: Use primary link in applicable components
Browse files Browse the repository at this point in the history
  • Loading branch information
gethinwebster committed Aug 3, 2023
1 parent 2644d5d commit 629d464
Show file tree
Hide file tree
Showing 15 changed files with 363 additions and 318 deletions.
6 changes: 5 additions & 1 deletion pages/cards/hooks.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import { EmptyState, getMatchesCountText, paginationLabels, pageSizeOptions } fr
import ScreenshotArea from '../utils/screenshot-area';

export const cardDefinition: CardsProps.CardDefinition<Instance> = {
header: item => <Link fontSize="heading-m">{item.id}</Link>,
header: item => (
<Link fontSize="heading-m" href="#">
{item.id}
</Link>
),
sections: [
{
id: 'type',
Expand Down
10 changes: 8 additions & 2 deletions src/__tests__/__snapshots__/documenter.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -8184,15 +8184,21 @@ For other options see the documentation for <a> tag's
"type": "string",
},
Object {
"defaultValue": "'secondary'",
"description": "Determines the visual style of the link as follows:
- \`primary\` - Displays the link text with bold styling for sufficient contrast with surrounding text.
Use this for links where the context doesn't imply interactivity such as
\\"Learn more\\" links and links within paragraphs.
- \`secondary\` - Does not provide any additional indicators for interactivity (except for an underline when the user hovers over or focuses the link).
This can be used in cases where the interactivity is strongly implied by its context,
such as in a table or a list of external links.
- \`info\` - Use for \\"info\\" links that link to content in a help panel.",
- \`info\` - Use for \\"info\\" links that link to content in a help panel.
The default is \`secondary\`, except inside the following components where it defaults to \`primary\`:
- Table
- Cards
- Alert
- Popover
- Help Panel (main \`content\` only)
",
"inlineType": Object {
"name": "LinkProps.Variant",
"type": "union",
Expand Down
51 changes: 27 additions & 24 deletions src/alert/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useMergeRefs } from '../internal/hooks/use-merge-refs';
import { SomeRequired } from '../internal/types';
import { useInternalI18n } from '../i18n/context';
import { DATA_ATTR_ANALYTICS_ALERT } from '../internal/analytics/selectors';
import { LinkDefaultVariantContext } from '../internal/context/link-default-variant-context';

const typeToIcon: Record<AlertProps.Type, IconProps['name']> = {
error: 'status-negative',
Expand Down Expand Up @@ -79,32 +80,34 @@ export default function InternalAlert({
)}
ref={mergedRef}
>
<VisualContext contextName="alert">
<div className={clsx(styles.alert, styles[`type-${type}`])}>
<div className={clsx(styles.icon, styles.text)} role="img" aria-label={statusIconAriaLabel}>
<InternalIcon name={typeToIcon[type]} size={size} />
</div>
<div className={styles.body}>
<div className={clsx(styles.message, styles.text)}>
{header && <div className={styles.header}>{header}</div>}
<div className={styles.content}>{children}</div>
<LinkDefaultVariantContext.Provider value={{ defaultVariant: 'primary' }}>
<VisualContext contextName="alert">
<div className={clsx(styles.alert, styles[`type-${type}`])}>
<div className={clsx(styles.icon, styles.text)} role="img" aria-label={statusIconAriaLabel}>
<InternalIcon name={typeToIcon[type]} size={size} />
</div>
{hasAction && <div className={styles.action}>{actionButton}</div>}
</div>
{dismissible && (
<div className={styles.dismiss}>
<InternalButton
className={styles['dismiss-button']}
variant="icon"
iconName="close"
formAction="none"
ariaLabel={i18n('dismissAriaLabel', dismissAriaLabel)}
onClick={() => fireNonCancelableEvent(onDismiss)}
/>
<div className={styles.body}>
<div className={clsx(styles.message, styles.text)}>
{header && <div className={styles.header}>{header}</div>}
<div className={styles.content}>{children}</div>
</div>
{hasAction && <div className={styles.action}>{actionButton}</div>}
</div>
)}
</div>
</VisualContext>
{dismissible && (
<div className={styles.dismiss}>
<InternalButton
className={styles['dismiss-button']}
variant="icon"
iconName="close"
formAction="none"
ariaLabel={i18n('dismissAriaLabel', dismissAriaLabel)}
onClick={() => fireNonCancelableEvent(onDismiss)}
/>
</div>
)}
</div>
</VisualContext>
</LinkDefaultVariantContext.Provider>
</div>
);
}
2 changes: 1 addition & 1 deletion src/breadcrumb-group/item/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
}

> .anchor {
@include styles.link-default;
@include styles.link-inline;

@include focus-visible.when-visible {
@include styles.link-focus;
Expand Down
111 changes: 58 additions & 53 deletions src/cards/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { supportsStickyPosition } from '../internal/utils/dom';
import { useInternalI18n } from '../i18n/context';
import { useContainerQuery } from '@cloudscape-design/component-toolkit';
import { AnalyticsFunnelSubStep } from '../internal/analytics/components/analytics-funnel';
import { LinkDefaultVariantContext } from '../internal/context/link-default-variant-context';

export { CardsProps };

Expand Down Expand Up @@ -127,59 +128,63 @@ const Cards = React.forwardRef(function <T = any>(
}

return (
<AnalyticsFunnelSubStep>
<div {...baseProps} className={clsx(baseProps.className, styles.root)} ref={mergedRef}>
<InternalContainer
header={
hasToolsHeader && (
<div
className={clsx(
styles.header,
isRefresh && styles['header-refresh'],
styles[`header-variant-${computedVariant}`]
)}
>
<ToolsHeader header={header} filter={filter} pagination={pagination} preferences={preferences} />
</div>
)
}
footer={hasFooterPagination && <div className={styles['footer-pagination']}>{pagination}</div>}
disableContentPaddings={true}
disableHeaderPaddings={computedVariant === 'full-page'}
variant={computedVariant === 'container' ? 'cards' : computedVariant}
__stickyHeader={stickyHeader}
__stickyOffset={stickyHeaderVerticalOffset}
__headerRef={headerRef}
__headerId={cardsHeaderId}
__darkHeader={computedVariant === 'full-page'}
__disableFooterDivider={true}
>
<div className={clsx(hasToolsHeader && styles['has-header'])}>
{!!renderAriaLive && !!firstIndex && (
<LiveRegion>
<span>{renderAriaLive({ totalItemsCount, firstIndex, lastIndex: firstIndex + items.length - 1 })}</span>
</LiveRegion>
)}
{status ?? (
<CardsList
items={items}
cardDefinition={cardDefinition}
trackBy={trackBy}
selectionType={selectionType}
columns={columns}
isItemSelected={isItemSelected}
getItemSelectionProps={getItemSelectionProps}
visibleSections={visibleSections}
updateShiftToggle={updateShiftToggle}
onFocus={onCardFocus}
ariaLabel={ariaLabels?.cardsLabel}
ariaLabelledby={ariaLabels?.cardsLabel ? undefined : cardsHeaderId}
/>
)}
</div>
</InternalContainer>
</div>
</AnalyticsFunnelSubStep>
<LinkDefaultVariantContext.Provider value={{ defaultVariant: 'primary' }}>
<AnalyticsFunnelSubStep>
<div {...baseProps} className={clsx(baseProps.className, styles.root)} ref={mergedRef}>
<InternalContainer
header={
hasToolsHeader && (
<div
className={clsx(
styles.header,
isRefresh && styles['header-refresh'],
styles[`header-variant-${computedVariant}`]
)}
>
<ToolsHeader header={header} filter={filter} pagination={pagination} preferences={preferences} />
</div>
)
}
footer={hasFooterPagination && <div className={styles['footer-pagination']}>{pagination}</div>}
disableContentPaddings={true}
disableHeaderPaddings={computedVariant === 'full-page'}
variant={computedVariant === 'container' ? 'cards' : computedVariant}
__stickyHeader={stickyHeader}
__stickyOffset={stickyHeaderVerticalOffset}
__headerRef={headerRef}
__headerId={cardsHeaderId}
__darkHeader={computedVariant === 'full-page'}
__disableFooterDivider={true}
>
<div className={clsx(hasToolsHeader && styles['has-header'])}>
{!!renderAriaLive && !!firstIndex && (
<LiveRegion>
<span>
{renderAriaLive({ totalItemsCount, firstIndex, lastIndex: firstIndex + items.length - 1 })}
</span>
</LiveRegion>
)}
{status ?? (
<CardsList
items={items}
cardDefinition={cardDefinition}
trackBy={trackBy}
selectionType={selectionType}
columns={columns}
isItemSelected={isItemSelected}
getItemSelectionProps={getItemSelectionProps}
visibleSections={visibleSections}
updateShiftToggle={updateShiftToggle}
onFocus={onCardFocus}
ariaLabel={ariaLabels?.cardsLabel}
ariaLabelledby={ariaLabels?.cardsLabel ? undefined : cardsHeaderId}
/>
)}
</div>
</InternalContainer>
</div>
</AnalyticsFunnelSubStep>
</LinkDefaultVariantContext.Provider>
);
}) as CardsForwardRefType;

Expand Down
5 changes: 4 additions & 1 deletion src/help-panel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import useBaseComponent from '../internal/hooks/use-base-component';
import { HelpPanelProps } from './interfaces';
import LiveRegion from '../internal/components/live-region';
import { useInternalI18n } from '../i18n/context';
import { LinkDefaultVariantContext } from '../internal/context/link-default-variant-context';

export { HelpPanelProps };

Expand All @@ -30,7 +31,9 @@ export default function HelpPanel({ header, footer, children, loading, loadingTe
) : (
<div {...containerProps} ref={__internalRootRef}>
{header && <div className={clsx(styles.header)}>{header}</div>}
<div className={clsx(styles.content)}>{children}</div>
<LinkDefaultVariantContext.Provider value={{ defaultVariant: 'primary' }}>
<div className={clsx(styles.content)}>{children}</div>
</LinkDefaultVariantContext.Provider>
{footer && <div className={styles.footer}>{footer}</div>}
</div>
);
Expand Down
8 changes: 8 additions & 0 deletions src/internal/context/link-default-variant-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { createContext } from 'react';
import { LinkProps } from '../../link/interfaces';

export const LinkDefaultVariantContext = createContext<{ defaultVariant: LinkProps.Variant }>({
defaultVariant: 'secondary',
});
6 changes: 1 addition & 5 deletions src/link/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,10 @@ import InternalLink from './internal';
export { LinkProps };

const Link = React.forwardRef(
(
{ variant = 'secondary', fontSize = 'body-m', color = 'normal', external = false, ...props }: LinkProps,
ref: React.Ref<LinkProps.Ref>
) => {
({ fontSize = 'body-m', color = 'normal', external = false, ...props }: LinkProps, ref: React.Ref<LinkProps.Ref>) => {
const baseComponentProps = useBaseComponent('Link');
return (
<InternalLink
variant={variant}
fontSize={fontSize}
color={color}
external={external}
Expand Down
7 changes: 7 additions & 0 deletions src/link/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ export interface LinkProps extends BaseComponentProps {
* This can be used in cases where the interactivity is strongly implied by its context,
* such as in a table or a list of external links.
* - `info` - Use for "info" links that link to content in a help panel.
*
* The default is `secondary`, except inside the following components where it defaults to `primary`:
* - Table
* - Cards
* - Alert
* - Popover
* - Help Panel (main `content` only)
*/
variant?: LinkProps.Variant;

Expand Down
5 changes: 4 additions & 1 deletion src/link/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
getNameFromSelector,
getSubStepAllSelector,
} from '../internal/analytics/selectors';
import { LinkDefaultVariantContext } from '../internal/context/link-default-variant-context';

type InternalLinkProps = InternalBaseComponentProps &
Omit<LinkProps, 'variant'> & {
Expand All @@ -34,7 +35,7 @@ type InternalLinkProps = InternalBaseComponentProps &
const InternalLink = React.forwardRef(
(
{
variant = 'secondary',
variant: providedVariant,
fontSize = 'body-m',
color = 'normal',
external = false,
Expand All @@ -52,6 +53,8 @@ const InternalLink = React.forwardRef(
) => {
checkSafeUrl('Link', href);
const isButton = !href;
const { defaultVariant } = useContext(LinkDefaultVariantContext);
const variant = providedVariant || defaultVariant;
const specialStyles = ['top-navigation', 'link', 'recovery'];
const hasSpecialStyle = specialStyles.indexOf(variant) > -1;

Expand Down
21 changes: 12 additions & 9 deletions src/popover/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { usePortalModeClasses } from '../internal/hooks/use-portal-mode-classes'
import { useInternalI18n } from '../i18n/context';
import { useUniqueId } from '../internal/hooks/use-unique-id';
import { getFirstFocusable } from '../internal/components/focus-lock/utils';
import { LinkDefaultVariantContext } from '../internal/context/link-default-variant-context';

export interface InternalPopoverProps extends PopoverProps, InternalBaseComponentProps {
__onOpen?: NonCancelableEventHandler<null>;
Expand Down Expand Up @@ -141,15 +142,17 @@ function InternalPopover(
renderWithPortal={renderWithPortal}
zIndex={renderWithPortal ? 7000 : undefined}
>
<PopoverBody
dismissButton={dismissButton}
dismissAriaLabel={dismissAriaLabel}
header={header}
onDismiss={onDismiss}
overflowVisible="both"
>
{content}
</PopoverBody>
<LinkDefaultVariantContext.Provider value={{ defaultVariant: 'primary' }}>
<PopoverBody
dismissButton={dismissButton}
dismissAriaLabel={dismissAriaLabel}
header={header}
onDismiss={onDismiss}
overflowVisible="both"
>
{content}
</PopoverBody>
</LinkDefaultVariantContext.Provider>
</PopoverContainer>
)}
</div>
Expand Down
Loading

0 comments on commit 629d464

Please sign in to comment.