Skip to content

Commit

Permalink
feat: update uikit, improve a11y for sharepopover (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyzyl-ool authored Nov 2, 2023
1 parent 6cb70a4 commit eff2702
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 90 deletions.
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"@gravity-ui/prettier-config": "^1.0.1",
"@gravity-ui/stylelint-config": "^2.0.0",
"@gravity-ui/tsconfig": "^1.0.0",
"@gravity-ui/uikit": "^5.10.0",
"@gravity-ui/uikit": "^5.17.0",
"@storybook/addon-essentials": "^7.1.1",
"@storybook/cli": "^7.1.1",
"@storybook/preset-scss": "^1.0.3",
Expand Down Expand Up @@ -88,7 +88,7 @@
"typescript": "^4.9.5"
},
"peerDependencies": {
"@gravity-ui/uikit": "^5.10.0",
"@gravity-ui/uikit": "^5.12.0",
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
},
Expand Down
3 changes: 2 additions & 1 deletion src/components/HelpPopover/HelpPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {QuestionMarkIcon} from './QuestionMarkIcon';
import './HelpPopover.scss';

const b = block('help-popover');
const ICON_SIZE = 16;

export interface HelpPopoverProps extends Omit<PopoverProps, 'children'>, QAProps {
buttonProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
Expand All @@ -25,7 +26,7 @@ export function HelpPopover(props: HelpPopoverProps) {
{...props.buttonProps}
className={b('button', props.buttonProps?.className)}
>
<Icon data={QuestionMarkIcon} size={16} />
<Icon data={QuestionMarkIcon} size={ICON_SIZE} />
</button>
</Popover>
);
Expand Down
4 changes: 4 additions & 0 deletions src/components/SharePopover/SharePopover.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@use '../variables';
@use '../mixins';

$block: '.#{variables.$ns}share-popover';

Expand All @@ -11,11 +12,14 @@ $block: '.#{variables.$ns}share-popover';
}

&__container {
@include mixins.button-reset();
@include mixins.focusable();
display: flex;
flex-wrap: nowrap;

cursor: pointer;
color: var(--g-color-text-secondary);
border-radius: var(--g-focus-border-radius);

&:hover {
color: var(--g-color-text-primary);
Expand Down
189 changes: 107 additions & 82 deletions src/components/SharePopover/SharePopover.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import React, {useMemo} from 'react';

import {NodesRight} from '@gravity-ui/icons';
import {Icon, Popover} from '@gravity-ui/uikit';
import {Icon, Popover, useUniqId} from '@gravity-ui/uikit';
import type {IconData, PopupPlacement} from '@gravity-ui/uikit';

import {block} from '../utils/cn';
Expand All @@ -13,6 +13,21 @@ import {LayoutDirection} from './constants';
import './SharePopover.scss';

const b = block('share-popover');
const DEFAULT_ICON_SIZE = 16; // px
const DEFAULT_CLOSE_DELAY = 300; // ms
const DEFAULT_PLACEMENT = 'bottom-end';

export const sharePopoverDefaultProps: SharePopoverDefaultProps = {
iconSize: DEFAULT_ICON_SIZE,
shareOptions: ShareList.defaultProps.shareOptions,
withCopyLink: true,
useWebShareApi: false,
placement: [DEFAULT_PLACEMENT],
openByHover: true,
autoclosable: true,
closeDelay: DEFAULT_CLOSE_DELAY,
direction: LayoutDirection.Row,
};

interface SharePopoverDefaultProps extends ShareListDefaultProps {
/** Web Share API setting (share options can be specified for non supported api case) */
Expand Down Expand Up @@ -60,51 +75,37 @@ export interface SharePopoverProps extends ShareListProps, Partial<SharePopoverD
}) => React.ReactElement;
}

type SharePopoverInnerProps = Omit<SharePopoverProps, keyof SharePopoverDefaultProps> &
Required<Pick<SharePopoverProps, keyof SharePopoverDefaultProps>>;

export const sharePopoverDefaultProps: SharePopoverDefaultProps = {
iconSize: 16,
shareOptions: ShareList.defaultProps.shareOptions,
withCopyLink: true,
useWebShareApi: false,
placement: ['bottom-end'],
openByHover: true,
autoclosable: true,
closeDelay: 300,
direction: LayoutDirection.Row,
};

export class SharePopover extends React.PureComponent<SharePopoverInnerProps> {
static defaultProps = sharePopoverDefaultProps;

render() {
const {
url,
title,
text,
shareOptions,
withCopyLink,
useWebShareApi,
placement,
openByHover,
autoclosable,
closeDelay,
iconSize,
iconClass,
tooltipClassName,
switcherClassName,
className,
direction,
customIcon,
buttonTitle,
copyTitle,
copyIcon,
renderCopy,
children,
} = this.props;

const content = (
export const SharePopover = (props: SharePopoverProps) => {
const {
url,
title,
text,
shareOptions = sharePopoverDefaultProps.shareOptions,
withCopyLink = sharePopoverDefaultProps.withCopyLink,
useWebShareApi = sharePopoverDefaultProps.useWebShareApi,
placement = sharePopoverDefaultProps.placement,
openByHover = sharePopoverDefaultProps.openByHover,
autoclosable = sharePopoverDefaultProps.autoclosable,
closeDelay = sharePopoverDefaultProps.closeDelay,
iconSize = sharePopoverDefaultProps.iconSize,
iconClass,
tooltipClassName,
switcherClassName,
className,
direction = sharePopoverDefaultProps.direction,
customIcon,
buttonTitle,
copyTitle,
copyIcon,
renderCopy,
children,
onClick,
} = props;
const [isOpen, setIsOpen] = React.useState(false);
const tooltipId = useUniqId();

const content = useMemo(
() => (
<ShareList
url={url}
title={title}
Expand All @@ -118,21 +119,60 @@ export class SharePopover extends React.PureComponent<SharePopoverInnerProps> {
>
{children}
</ShareList>
);

return (
<Popover
placement={placement}
hasArrow={false}
openOnHover={openByHover && !useWebShareApi}
autoclosable={autoclosable}
delayClosing={closeDelay}
content={content}
className={b(null, className)}
tooltipClassName={b('tooltip', tooltipClassName)}
onClick={this.handleClick}
>
<div className={b('container', switcherClassName)}>
),
[
children,
copyIcon,
copyTitle,
direction,
renderCopy,
shareOptions,
text,
title,
url,
withCopyLink,
],
);

const handleClick = React.useCallback(
async (event: React.MouseEvent<HTMLSpanElement>) => {
if (onClick) {
onClick(event);
}

if (useWebShareApi && navigator && typeof navigator.share === 'function') {
await navigator.share({url, title, text});
event.preventDefault();
return false;
}
return true;
},
[onClick, text, title, url, useWebShareApi],
);

return (
<Popover
placement={placement}
hasArrow={false}
openOnHover={openByHover && !useWebShareApi}
autoclosable={autoclosable}
delayClosing={closeDelay}
content={content}
className={b(null, className)}
tooltipClassName={b('tooltip', tooltipClassName)}
onClick={handleClick}
tooltipId={tooltipId}
disablePortal
onOpenChange={setIsOpen}
>
{({onClick: onClickInner}) => (
<button
className={b('container', switcherClassName)}
aria-expanded={openByHover ? undefined : isOpen}
aria-controls={tooltipId}
aria-describedby={tooltipId}
onClick={onClickInner}
>
<div className={b('icon-container')}>
<Icon
data={customIcon ? customIcon : NodesRight}
Expand All @@ -142,23 +182,8 @@ export class SharePopover extends React.PureComponent<SharePopoverInnerProps> {
</div>

{Boolean(buttonTitle) && <div className={b('title')}>{buttonTitle}</div>}
</div>
</Popover>
);
}

private handleClick = async (event: React.MouseEvent<HTMLSpanElement>) => {
const {url, title, text, useWebShareApi, onClick} = this.props;

if (onClick) {
onClick(event);
}

if (useWebShareApi && navigator && typeof navigator.share === 'function') {
await navigator.share({url, title, text});
event.preventDefault();
return false;
}
return true;
};
}
</button>
)}
</Popover>
);
};
10 changes: 10 additions & 0 deletions src/components/mixins.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@import '@gravity-ui/uikit/styles/mixins';

@mixin focusable() {
&:focus {
outline: 2px solid var(--g-color-line-focus);
}
&:focus:not(:focus-visible) {
outline: 0;
}
}

0 comments on commit eff2702

Please sign in to comment.