Skip to content
This repository has been archived by the owner on Jan 5, 2023. It is now read-only.

Commit

Permalink
Merge branch 'main' into mayank/monorepo
Browse files Browse the repository at this point in the history
  • Loading branch information
mayank99 committed Apr 20, 2022
1 parent 112ecd1 commit be2eee7
Show file tree
Hide file tree
Showing 23 changed files with 397 additions and 29 deletions.
2 changes: 1 addition & 1 deletion packages/iTwinUI-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"build-storybook": "build-storybook"
},
"dependencies": {
"@itwin/itwinui-css": "^0.54.1",
"@itwin/itwinui-css": "^0.55.0",
"@itwin/itwinui-icons-react": "^1.5.0",
"@itwin/itwinui-illustrations-react": "^1.0.1",
"@tippyjs/react": "^4.2.5",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ it('should render menuItems correctly', () => {
);

const button = container.querySelector(
'.iui-header-button.iui-dropdown.iui-borderless',
'.iui-header-dropdown-button',
) as HTMLButtonElement;
expect(button).toBeTruthy();

Expand Down
4 changes: 3 additions & 1 deletion packages/iTwinUI-react/src/core/Header/HeaderButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const isSplitButton = (
};

const isDropdownButton = (
props: Partial<DropdownButtonProps>,
props: Omit<Partial<DropdownButtonProps>, 'name'>,
): props is DropdownButtonProps => {
return !!props.menuItems;
};
Expand Down Expand Up @@ -89,6 +89,8 @@ export const HeaderButton: HeaderButtonComponent = React.forwardRef(
{
'iui-header-button': !isSplitButton(props),
'iui-header-split-button': isSplitButton(props),
'iui-header-dropdown-button':
!isSplitButton(props) && isDropdownButton(props),
'iui-active': isActive,
},
className,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import React from 'react';
import { render } from '@testing-library/react';
import SkipToContentLink from './SkipToContentLink';

it('should render link in its most basic state', () => {
const { container } = render(<SkipToContentLink href='#main-content-id' />);
const link = container.querySelector(
'.iui-skip-to-content-link',
) as HTMLElement;
expect(link).toBeTruthy();
expect(link.textContent).toBe('Skip to main content');
expect(link).toHaveAttribute('href', '#main-content-id');
});

it('should render link with children', () => {
const { container } = render(
<SkipToContentLink href='#main-content-id'>
Custom child text
</SkipToContentLink>,
);

const link = container.querySelector(
'.iui-skip-to-content-link',
) as HTMLElement;
expect(link).toBeTruthy();
expect(link.textContent).toBe('Custom child text');
});

it('should propagate misc props in link', () => {
const { container } = render(
<SkipToContentLink
href='#main-content-id'
className='test-class'
style={{ color: 'red' }}
aria-label='test-label'
id='test-id'
/>,
);
const link = container.querySelector(
'.iui-skip-to-content-link',
) as HTMLElement;
console.log(link);
expect(link).toHaveAttribute('href', '#main-content-id');
expect(link).toHaveClass('test-class');
expect(link).toHaveStyle('color: red');
expect(link).toHaveAttribute('aria-label', 'test-label');
expect(link).toHaveAttribute('id', 'test-id');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import React from 'react';
import cx from 'classnames';
import { useTheme } from '../utils';
import '@itwin/itwinui-css/css/skip-to-content.css';

export type SkipToContentLinkProps = {
/**
* The id of the main content that the link directs to. Don't forget the #!
*/
href: string;
/**
* Localize 'Skip to main content' label.
* @default 'Skip to main content'
*/
children?: React.ReactNode;
} & Omit<React.ComponentPropsWithoutRef<'a'>, 'href'>;

/**
* `SkipToContentLink` is for screen reader and keyboard users and will not be visible unless tabbed to.
* Provides a shortcut to the main content of the page without navigating through the header, etc.
* Should be placed just inside the body. Set `href` to the id of your main content component. Don't forget the `#`!
* @example
* <body><SkipToContentLink href='#main-content-id' /> ... </body>
* <body><SkipToContentLink href='#main-content-id'>{localizedLabel}</SkipToContentLink> ... </body>
*/
export const SkipToContentLink = React.forwardRef<
HTMLAnchorElement,
SkipToContentLinkProps
>((props, ref) => {
const { children = 'Skip to main content', className, ...rest } = props;

useTheme();

return (
<a
ref={ref}
className={cx('iui-skip-to-content-link', className)}
{...rest}
>
{children}
</a>
);
});

export default SkipToContentLink;
7 changes: 7 additions & 0 deletions packages/iTwinUI-react/src/core/SkipToContentLink/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
export { SkipToContentLink } from './SkipToContentLink';
export type { SkipToContentLinkProps } from './SkipToContentLink';
export default './SkipToContentLink';
41 changes: 41 additions & 0 deletions packages/iTwinUI-react/src/core/Table/Table.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,18 @@ it('should not select when clicked on row but selectRowOnClick flag is false', (
expect(onRowClick).toHaveBeenCalled();
});

it('should not select when clicked on row and preventDefault is set', () => {
const onSelect = jest.fn();
renderComponent({
isSelectable: true,
onSelect,
rowProps: () => ({ onClick: (e) => e.preventDefault() }),
});

userEvent.click(screen.getByText(mockedData()[1].name));
expect(onSelect).not.toHaveBeenCalled();
});

it('should not trigger onSelect when sorting and filtering', () => {
const onSort = jest.fn();
const onSelect = jest.fn();
Expand Down Expand Up @@ -2839,3 +2851,32 @@ it('should show column enabled when whole row is disabled', () => {
expect(rowCells[0].classList).not.toContain('iui-disabled');
expect(rowCells[1].classList).toContain('iui-disabled');
});

it('should render selectable rows without select column', () => {
const onRowClick = jest.fn();
const { container, getByText } = renderComponent({
isSelectable: true,
selectionMode: 'single',
onRowClick,
});

const rows = container.querySelectorAll('.iui-table-body .iui-row');
expect(rows.length).toBe(3);

expect(container.querySelectorAll('.iui-slot .iui-checkbox').length).toBe(0);

userEvent.click(getByText(mockedData()[1].name));
expect(rows[1].classList).toContain('iui-selected');
expect(onRowClick).toHaveBeenCalledTimes(1);

userEvent.click(getByText(mockedData()[2].name));
expect(rows[1].classList).not.toContain('iui-selected');
expect(rows[2].classList).toContain('iui-selected');
expect(onRowClick).toHaveBeenCalledTimes(2);

//Test that ctrl clicking doesn't highlight more than one row
userEvent.click(getByText(mockedData()[1].name), { ctrlKey: true });
expect(rows[1].classList).toContain('iui-selected');
expect(rows[2].classList).not.toContain('iui-selected');
expect(onRowClick).toHaveBeenCalledTimes(3);
});
33 changes: 26 additions & 7 deletions packages/iTwinUI-react/src/core/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ export type TableProps<
* Handler for when a row is clicked. Must be memoized.
*/
onRowClick?: (event: React.MouseEvent, row: Row<T>) => void;
/**
* Modify the selection mode of the table.
* The column with checkboxes will not be present with 'single' selection mode.
* @default 'multi'
*/
selectionMode?: 'multi' | 'single';
/**
* Flag whether table columns can be sortable.
* @default false
Expand Down Expand Up @@ -296,6 +302,7 @@ export const Table = <
isSelectable = false,
onSelect,
onRowClick,
selectionMode = 'multi',
isSortable = false,
onSort,
stateReducer,
Expand Down Expand Up @@ -455,7 +462,7 @@ export const Table = <
useRowSelect,
useSubRowSelection,
useExpanderCell(subComponent, expanderCell, isRowDisabled),
useSelectionCell(isSelectable, isRowDisabled),
useSelectionCell(isSelectable, selectionMode, isRowDisabled),
useColumnOrder,
useColumnDragAndDrop(enableColumnReordering),
);
Expand Down Expand Up @@ -491,8 +498,16 @@ export const Table = <
const onRowClickHandler = React.useCallback(
(event: React.MouseEvent, row: Row<T>) => {
const isDisabled = isRowDisabled?.(row.original);
if (isSelectable && !isDisabled && selectRowOnClick) {
if (!row.isSelected && !event.ctrlKey) {
if (!isDisabled) {
onRowClick?.(event, row);
}
if (
isSelectable &&
!isDisabled &&
selectRowOnClick &&
!event.isDefaultPrevented()
) {
if (!row.isSelected && (selectionMode === 'single' || !event.ctrlKey)) {
dispatch({
type: singleRowSelectedAction,
id: row.id,
Expand All @@ -501,11 +516,15 @@ export const Table = <
row.toggleRowSelected(!row.isSelected);
}
}
if (!isDisabled) {
onRowClick?.(event, row);
}
},
[isRowDisabled, isSelectable, selectRowOnClick, dispatch, onRowClick],
[
isRowDisabled,
isSelectable,
selectRowOnClick,
selectionMode,
dispatch,
onRowClick,
],
);

React.useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import { SelectionColumn, SELECTION_CELL_ID } from '../columns';

export const useSelectionCell = <T extends Record<string, unknown>>(
isSelectable: boolean,
selectionMode: 'multi' | 'single',
isRowDisabled?: (rowData: T) => boolean,
) => (hooks: Hooks<T>) => {
if (!isSelectable) {
return;
}

hooks.allColumns.push((columns: ColumnInstance<T>[]) =>
selectionMode === 'single' ||
columns.find((c) => c.id === SELECTION_CELL_ID)
? columns
: [SelectionColumn({ isDisabled: isRowDisabled }), ...columns],
Expand Down
14 changes: 7 additions & 7 deletions packages/iTwinUI-react/src/core/Tag/Tag.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ it('renders in its most basic state', () => {
const { container } = render(<Tag>Mocked tag</Tag>);
expect(container.querySelector('.iui-tag')).toBeTruthy();

const text = container.querySelector('.iui-label') as HTMLElement;
const text = container.querySelector('.iui-tag-label') as HTMLElement;
expect(text).toBeTruthy();
expect(text.textContent).toBe('Mocked tag');

expect(container.querySelector('.iui-borderless')).toBeNull();
expect(container.querySelector('.iui-tag-button')).toBeNull();
});

it('should propagate custom styles and className', () => {
Expand All @@ -34,7 +34,7 @@ it('fires close event on click', () => {
const result = render(<Tag onRemove={fn}>Mocked tag</Tag>);

const close = result.container.querySelector(
'.iui-borderless',
'.iui-tag-button',
) as HTMLElement;
expect(close).toBeTruthy();
fireEvent.click(close);
Expand All @@ -43,9 +43,9 @@ it('fires close event on click', () => {

it('should render correctly with basic variant', () => {
const { container } = render(<Tag variant='basic'>Mocked tag</Tag>);
expect(container.querySelector('.iui-tag.iui-basic')).toBeTruthy();
expect(container.querySelector('.iui-tag-basic')).toBeTruthy();

const text = container.querySelector('.iui-label') as HTMLElement;
expect(text).toBeTruthy();
expect(text.textContent).toBe('Mocked tag');
const text = container.querySelector('.iui-tag-label') as HTMLElement;
expect(text).not.toBeTruthy();
expect(container.textContent).toBe('Mocked tag');
});
15 changes: 13 additions & 2 deletions packages/iTwinUI-react/src/core/Tag/Tag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,27 @@ export const Tag = (props: TagProps) => {

return (
<span
className={cx('iui-tag', { 'iui-basic': variant === 'basic' }, className)}
className={cx(
{
'iui-tag-basic': variant === 'basic',
'iui-tag': variant === 'default',
},
className,
)}
{...rest}
>
<span className='iui-label'>{children}</span>
{variant === 'default' ? (
<span className='iui-tag-label'>{children}</span>
) : (
children
)}
{onRemove && (
<IconButton
styleType='borderless'
size='small'
onClick={onRemove}
aria-label='Delete tag'
className='iui-tag-button'
>
<SvgCloseSmall aria-hidden />
</IconButton>
Expand Down
8 changes: 4 additions & 4 deletions packages/iTwinUI-react/src/core/Tag/TagContainer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ it('should render correctly', () => {
</TagContainer>,
);
expect(container.querySelector('.iui-tag-container')).toBeTruthy();
expect(container.querySelectorAll('.iui-tag').length).toEqual(2);
expect(container.querySelectorAll('.iui-tag-basic').length).toEqual(2);
});

it('should propagate custom styles and className', () => {
Expand Down Expand Up @@ -44,7 +44,7 @@ it('should render scroll container', () => {
</TagContainer>,
);
expect(container.querySelector('.iui-tag-container.iui-scroll')).toBeTruthy();
expect(container.querySelectorAll('.iui-tag').length).toEqual(4);
expect(container.querySelectorAll('.iui-tag-basic').length).toEqual(4);
});

it('should render truncated container', () => {
Expand All @@ -59,7 +59,7 @@ it('should render truncated container', () => {
expect(
container.querySelector('.iui-tag-container.iui-truncate'),
).toBeTruthy();
expect(container.querySelectorAll('.iui-tag').length).toEqual(4);
expect(container.querySelectorAll('.iui-tag-basic').length).toEqual(4);
});

it('should render filled background', () => {
Expand All @@ -74,5 +74,5 @@ it('should render filled background', () => {
expect(
container.querySelector('.iui-tag-container.iui-visible'),
).toBeTruthy();
expect(container.querySelectorAll('.iui-tag').length).toEqual(4);
expect(container.querySelectorAll('.iui-tag-basic').length).toEqual(4);
});
3 changes: 3 additions & 0 deletions packages/iTwinUI-react/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ export type {
SidenavSubmenuHeaderProps,
} from './SideNavigation';

export { SkipToContentLink } from './SkipToContentLink';
export type { SkipToContentLinkProps } from './SkipToContentLink';

export { Slider } from './Slider';
export type { SliderProps } from './Slider';

Expand Down

0 comments on commit be2eee7

Please sign in to comment.