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

Merge main into dev #615

Merged
merged 8 commits into from
May 10, 2023
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

### [2.36.2](https://github.com/EightfoldAI/octuple/compare/v2.36.1...v2.36.2) (2023-05-09)

### Bug Fixes

- stepper: handle scrollIntoView for both layouts on mount ([#613](https://github.com/EightfoldAI/octuple/issues/613)) ([209d5c3](https://github.com/EightfoldAI/octuple/commits/209d5c3cd492991e67cd854632cdf00193b32b6c))

### [2.36.1](https://github.com/EightfoldAI/octuple/compare/v2.36.0...v2.36.1) (2023-05-04)

### Bug Fixes

- select: ensure aria attributes target the intended element ([#612](https://github.com/EightfoldAI/octuple/issues/612)) ([387158d](https://github.com/EightfoldAI/octuple/commits/387158d892270c8317eb7a4dab4dfc5c0473398e))

## [2.36.0](https://github.com/EightfoldAI/octuple/compare/v2.35.3...v2.36.0) (2023-05-03)

### Features

- adding style option for Empty description ([#606](https://github.com/EightfoldAI/octuple/issues/606)) ([0539a61](https://github.com/EightfoldAI/octuple/commits/0539a61868990ba4e636a559b2ad919af72424a1))
- linkbutton: add link button component ([#607](https://github.com/EightfoldAI/octuple/issues/607)) ([8b3d782](https://github.com/EightfoldAI/octuple/commits/8b3d782e20146413b623159ad280aaeb3360aed4))

### Bug Fixes

- dropdown: use id from clone element props if present ([#608](https://github.com/EightfoldAI/octuple/issues/608)) ([6db0dc9](https://github.com/EightfoldAI/octuple/commits/6db0dc90eb628091c45e03cfa7c7d624b06a312c))
- pagination: Issues with VisiblePagerCountSize ([#610](https://github.com/EightfoldAI/octuple/issues/610)) ([919c7c5](https://github.com/EightfoldAI/octuple/commits/919c7c5248a700a8e866ce2eeeac83ae3e9107b1))

### [2.35.3](https://github.com/EightfoldAI/octuple/compare/v2.35.2...v2.35.3) (2023-04-24)

### Bug Fixes
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@eightfold.ai/octuple",
"version": "2.35.3",
"version": "2.36.2",
"license": "MIT",
"main": "lib/octuple.js",
"types": "lib/octuple.d.ts",
Expand Down
1 change: 1 addition & 0 deletions src/components/Button/DefaultButton/DefaultButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const DefaultButton: FC<ButtonProps> = React.forwardRef(
classNames={classNames}
counter={counter}
disabled={disabled}
disruptive={disruptive}
dropShadow={dropShadow}
floatingButtonProps={floatingButtonProps}
htmlType={htmlType}
Expand Down
1 change: 1 addition & 0 deletions src/components/Button/SystemUIButton/SystemUIButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export const SystemUIButton: FC<ButtonProps> = React.forwardRef(
style={style}
text={text}
toggle={toggle}
transparent={transparent}
variant={ButtonVariant.SystemUI}
/>
);
Expand Down
87 changes: 68 additions & 19 deletions src/components/Dropdown/Dropdown.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useRef, useState } from 'react';
import Enzyme from 'enzyme';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
import MatchMediaMock from 'jest-matchmedia-mock';
Expand All @@ -9,8 +9,10 @@ import {
ButtonWidth,
DefaultButton,
} from '../Button';
import { IconName } from '../Icon';
import { Icon, IconName } from '../Icon';
import { List } from '../List';
import { Stack } from '../Stack';
import { TextInput } from '../Inputs';
import { render, screen, waitFor } from '@testing-library/react';

Enzyme.configure({ adapter: new Adapter() });
Expand Down Expand Up @@ -46,26 +48,26 @@ const Overlay = () => (
/>
);

const dropdownProps: object = {
trigger: 'click',
classNames: 'my-dropdown-class',
style: {},
dropdownClassNames: 'my-dropdown-class',
dropdownStyle: {
color: 'red',
},
placement: 'bottom-start',
overlay: Overlay(),
offset: 0,
positionStrategy: 'absolute',
disabled: false,
closeOnDropdownClick: true,
portal: false,
};

const DropdownComponent = (): JSX.Element => {
const [visible, setVisibility] = useState(false);

const dropdownProps: object = {
trigger: 'click',
classNames: 'my-dropdown-class',
style: {},
dropdownClassNames: 'my-dropdown-class',
dropdownStyle: {
color: 'red',
},
placement: 'bottom-start',
overlay: Overlay(),
offset: 0,
positionStrategy: 'absolute',
disabled: false,
closeOnDropdownClick: true,
portal: false,
};

return (
<Dropdown
{...dropdownProps}
Expand All @@ -84,6 +86,36 @@ const DropdownComponent = (): JSX.Element => {
);
};

const ComplexDropdownComponent = (): JSX.Element => {
const inputRef: React.MutableRefObject<HTMLInputElement> =
useRef<HTMLInputElement>(null);
const [visible, setVisibility] = useState(false);

return (
<Dropdown
{...dropdownProps}
ariaRef={inputRef}
onVisibleChange={(isVisible) => setVisibility(isVisible)}
>
<div id="wrapper">
<Stack direction="horizontal" flexGap="xl">
<Icon path={IconName.mdiAccount} />
<TextInput
ref={inputRef}
placeholder={'Select'}
iconProps={{
path: IconName.mdiChevronDown,
rotate: visible ? 180 : 0,
}}
role="combobox"
data-testid="test-input-id"
/>
</Stack>
</div>
</Dropdown>
);
};

describe('Dropdown', () => {
beforeAll(() => {
matchMedia = new MatchMediaMock();
Expand Down Expand Up @@ -132,4 +164,21 @@ describe('Dropdown', () => {
const dropdownButton = screen.getByRole('button');
expect(dropdownButton.id).toBe('test-button-id');
});

test('Should support ariaRef prop for complex dropdown references', async () => {
const { container } = render(<ComplexDropdownComponent />);
const dropdownAriaRef = screen.getByTestId('test-input-id');
expect(dropdownAriaRef.getAttribute('aria-controls')).toBeTruthy();
expect(dropdownAriaRef.getAttribute('aria-expanded')).toBe('false');
expect(dropdownAriaRef.getAttribute('aria-haspopup')).toBe('true');
expect(dropdownAriaRef.getAttribute('role')).toBe('combobox');
dropdownAriaRef.click();
await waitFor(() => screen.getByText('User profile 1'));
const option1 = screen.getByText('User profile 1');
expect(option1).toBeTruthy();
expect(container.querySelector('.dropdown-wrapper')?.classList).toContain(
'my-dropdown-class'
);
expect(dropdownAriaRef.getAttribute('aria-expanded')).toBe('true');
});
});
22 changes: 22 additions & 0 deletions src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const Dropdown: FC<DropdownProps> = React.memo(
React.forwardRef<DropdownRef, DropdownProps>(
(
{
ariaRef,
children,
classNames,
closeOnDropdownClick = true,
Expand Down Expand Up @@ -243,6 +244,27 @@ export const Dropdown: FC<DropdownProps> = React.memo(
if (child.props.id && dropdownReferenceId !== child.props.id) {
setReferenceElementId(child.props.id);
}
// If there's an ariaRef, apply the a11y attributes to it, rather than the immediate child.
if (ariaRef && ariaRef.current) {
ariaRef.current.setAttribute('aria-controls', dropdownId);
ariaRef.current.setAttribute('aria-expanded', `${mergedVisible}`);
ariaRef.current.setAttribute('aria-haspopup', 'true');

if (!ariaRef.current.hasAttribute('role')) {
ariaRef.current.setAttribute('role', 'button');
}

return cloneElement(child, {
...{
[TRIGGER_TO_HANDLER_MAP_ON_ENTER[trigger]]: toggle(true),
},
id: dropdownReferenceId,
onClick: handleReferenceClick,
onKeyDown: handleReferenceKeyDown,
className: referenceWrapperClassNames,
});
}

return cloneElement(child, {
...{
[TRIGGER_TO_HANDLER_MAP_ON_ENTER[trigger]]: toggle(true),
Expand Down
6 changes: 6 additions & 0 deletions src/components/Dropdown/Dropdown.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ export const TRIGGER_TO_HANDLER_MAP_ON_LEAVE = {
};

export interface DropdownProps {
/**
* The ref of element that should implement the following props:
* 'aria-controls', 'aria-expanded', 'aria-haspopup', 'role'
* @default child
*/
ariaRef?: React.MutableRefObject<HTMLElement>;
/**
* Class names of the main wrapper
*/
Expand Down
47 changes: 31 additions & 16 deletions src/components/Pagination/Pager.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { FC, useEffect, useCallback, useState, Ref } from 'react';
import {
PagerProps,
PagerSizeOptions,
PaginationVisiblePagerCountSizeOptions,
} from './Pagination.types';
import { ButtonShape, ButtonSize, NeutralButton } from '../Button';
Expand All @@ -9,19 +10,18 @@ import { mergeClasses } from '../../shared/utilities';

import styles from './pagination.module.scss';

/** Represents the number of pages from each edge of the list before we show quick buttons. */
const EDGE_BUFFER_THRESHOLD: number = 5;

/** Represents a list too short to display meaningful quick buttons. */
const SHORT_LIST_THRESHOLD: number = 10;

/** Represents the number of list items (pages) visible at any given time. */
const VISIBLE_PAGER_COUNT = {
[PaginationVisiblePagerCountSizeOptions.Small]: 3,
[PaginationVisiblePagerCountSizeOptions.Medium]: 5,
[PaginationVisiblePagerCountSizeOptions.Large]: 7,
};

const PAGER_SIZE_COUNT = {
[PagerSizeOptions.Small]: 5,
[PagerSizeOptions.Medium]: 7,
};

export const Pager: FC<PagerProps> = React.forwardRef(
(
{
Expand All @@ -33,17 +33,32 @@ export const Pager: FC<PagerProps> = React.forwardRef(
quickPreviousIconButtonAriaLabel,
simplified = false,
showLast = true,
pagerSize,
visiblePagerCountSize = PaginationVisiblePagerCountSizeOptions.Large,
...rest
},
ref: Ref<HTMLUListElement>
) => {
/** Represents the number of pages from each edge of the list before we show quick buttons. */
let edgeBufferThreshold: number = 5;
/** Represents a list too short to display meaningful quick buttons. */
let shortListThreshold: number = 10;

if (pagerSize) {
edgeBufferThreshold = PAGER_SIZE_COUNT[pagerSize] - 2;
shortListThreshold = PAGER_SIZE_COUNT[pagerSize] + 3;
}
const [_pagers, setPagers] = useState<number[]>([0]);
const [_quickNextActive, setQuickNextActive] = useState<boolean>(false);
const [_quickPreviousActive, setQuickPreviousActive] =
useState<boolean>(false);

const visiblePagerCount = VISIBLE_PAGER_COUNT?.[visiblePagerCountSize] || 7;
// TODO: Remove in Octuple v3.0.0 in favor of pagerSize prop
let visiblePagerCount = VISIBLE_PAGER_COUNT?.[visiblePagerCountSize] || 7;

if (pagerSize) {
visiblePagerCount = PAGER_SIZE_COUNT?.[pagerSize] || 7;
}

/**
* Updates the visible range of pages in the UL based upon list
Expand All @@ -58,7 +73,7 @@ export const Pager: FC<PagerProps> = React.forwardRef(
* The pageCount is greater than 10.
* Keeping the quick buttons meaningful.
*/
if (pageCount > SHORT_LIST_THRESHOLD) {
if (pageCount > shortListThreshold) {
/**
* The visible items in the array use a basic threshold.
* e.g. [1 << 4 5 [6] 7 8 >> 100] where 6 is the currentPage
Expand All @@ -67,9 +82,9 @@ export const Pager: FC<PagerProps> = React.forwardRef(
* the 5 items between the two quick buttons they must represent
* the correct position in the array.
*/
const afterQuickPrevious: boolean = currentPage > EDGE_BUFFER_THRESHOLD;
const afterQuickPrevious: boolean = currentPage > edgeBufferThreshold;
const beforeQuickNext: boolean =
currentPage < pageCount - EDGE_BUFFER_THRESHOLD;
currentPage < pageCount - edgeBufferThreshold;

/**
* The currentPage number is greater than the quick previous
Expand Down Expand Up @@ -164,8 +179,8 @@ export const Pager: FC<PagerProps> = React.forwardRef(
</li>
)}
{!simplified &&
currentPage > EDGE_BUFFER_THRESHOLD &&
pageCount > SHORT_LIST_THRESHOLD && (
currentPage > edgeBufferThreshold &&
pageCount > shortListThreshold && (
<li>
<NeutralButton
ariaLabel={quickPreviousIconButtonAriaLabel}
Expand All @@ -185,7 +200,7 @@ export const Pager: FC<PagerProps> = React.forwardRef(
onMouseEnter={() => setQuickPreviousActive(true)}
onMouseLeave={() => setQuickPreviousActive(false)}
onClick={() =>
onCurrentChange(currentPage - EDGE_BUFFER_THRESHOLD)
onCurrentChange(currentPage - edgeBufferThreshold)
}
size={ButtonSize.Medium}
/>
Expand All @@ -212,8 +227,8 @@ export const Pager: FC<PagerProps> = React.forwardRef(
);
})}
{!simplified &&
currentPage < pageCount - EDGE_BUFFER_THRESHOLD &&
pageCount > SHORT_LIST_THRESHOLD && (
currentPage < pageCount - edgeBufferThreshold &&
pageCount > shortListThreshold && (
<li>
<NeutralButton
ariaLabel={quickNextIconButtonAriaLabel}
Expand All @@ -233,7 +248,7 @@ export const Pager: FC<PagerProps> = React.forwardRef(
onMouseEnter={() => setQuickNextActive(true)}
onMouseLeave={() => setQuickNextActive(false)}
onClick={() =>
onCurrentChange(currentPage + EDGE_BUFFER_THRESHOLD)
onCurrentChange(currentPage + edgeBufferThreshold)
}
size={ButtonSize.Medium}
/>
Expand Down
8 changes: 2 additions & 6 deletions src/components/Pagination/Pagination.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import React from 'react';
import { Stories } from '@storybook/addon-docs';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import {
Pagination,
PaginationLayoutOptions,
PaginationVisiblePagerCountSizeOptions,
} from './index';
import { PagerSizeOptions, Pagination, PaginationLayoutOptions } from './index';

export default {
title: 'Pagination',
Expand Down Expand Up @@ -116,7 +112,7 @@ const paginationArgs: Object = {
restrictPageSizesPropToSizesLayout: false,
hideWhenSinglePage: false,
total: 50,
visiblePagerCountSize: PaginationVisiblePagerCountSizeOptions.Large,
pagerSize: PagerSizeOptions.Medium,
'data-test-id': 'myPaginationTestId',
};

Expand Down
4 changes: 4 additions & 0 deletions src/components/Pagination/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export const Pagination: FC<PaginationProps> = React.forwardRef(
onCurrentChange,
onSizeChange,
pageCount,
// TODO: Set a default once visiblePagerCountSize is removed
pagerSize,
pageSize = 10,
pageSizeButtonAriaLabel: defaultPageSizeButtonAriaLabel,
pageSizes = [10, 20, 30, 40, 50, 100],
Expand Down Expand Up @@ -393,6 +395,7 @@ export const Pagination: FC<PaginationProps> = React.forwardRef(
locale={locale}
onCurrentChange={handleCurrentChange}
pageCount={getPageCount()}
pagerSize={pagerSize}
quickNextIconButtonAriaLabel={
quickNextIconButtonAriaLabel
}
Expand All @@ -412,6 +415,7 @@ export const Pagination: FC<PaginationProps> = React.forwardRef(
locale={locale}
onCurrentChange={handleCurrentChange}
pageCount={getPageCount()}
pagerSize={pagerSize}
quickNextIconButtonAriaLabel={
quickNextIconButtonAriaLabel
}
Expand Down
Loading