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

Use the new Placement dictionary in Dropdown and Tooltip #DS-923 #1100

Merged
merged 2 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions packages/web-react/src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import React, { createElement, useRef, LegacyRef } from 'react';
import classNames from 'classnames';
import DropdownWrapper from './DropdownWrapper';
import React, { LegacyRef, createElement, useRef } from 'react';
import { Placements } from '../../constants';
import { useStyleProps } from '../../hooks';
import { DropdownPlacements, SpiritDropdownProps } from '../../types';
import { SpiritDropdownProps } from '../../types';
import DropdownWrapper from './DropdownWrapper';
import { useDropdown } from './useDropdown';
import { useDropdownStyleProps } from './useDropdownStyleProps';
import { useDropdownAriaProps } from './useDropdownAriaProps';
import { useDropdownStyleProps } from './useDropdownStyleProps';

const defaultProps = {
placement: DropdownPlacements.BOTTOM_LEFT,
enableAutoClose: true,
placement: Placements.BOTTOM_LEFT,
};

const Dropdown = (props: SpiritDropdownProps) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/web-react/src/components/Dropdown/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { Dropdown } from '@lmc-eu/spirit-web-react/components';
| `fullWidthMode` | [`DropdownFullwidthMode`][dropdownfullwidthmode] | `off` | ✕ | Full-width mode |
| `id` | `string` | `<random>` | ✕ | Component id |
| `onAutoClose` . | `(event: Event) => void` | — | ✕ | Callback on close on click outside of Dropdown |
| `placement` | [`DropdownPlacement`][dropdownplacement] | `bottom-left` | ✕ | Alignment of the component |
| `placement` | [Placement dictionary][dictionary-placement] | `bottom-left` | ✕ | Alignment of the component |
| `renderTrigger` | `(render: DropdownRenderProps) => ReactNode` | — | ✕ | Properties for trigger render |
| `UNSAFE_className` | `string` | — | ✕ | Wrapper custom classname |
| `UNSAFE_style` | `CSSProperties` | — | ✕ | Wrapper custom style |
Expand All @@ -40,6 +40,6 @@ import { Dropdown } from '@lmc-eu/spirit-web-react/components';
| `trigger.ref` | `LegacyRef<HTMLButtonElement & HTMLAnchorElement>` | Trigger reference |

[dropdown]: https://github.com/lmc-eu/spirit-design-system/tree/main/packages/web/src/scss/components/Dropdown
[dropdownplacement]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/src/types/dropdown.ts#L4
[dropdownbreakpoint]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/src/types/dropdown.ts#L11
[dropdownfullwidthmode]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/src/types/dropdown.ts#L19
[dictionary-placement]: https://github.com/lmc-eu/spirit-design-system/tree/main/docs/DICTIONARIES.md#placement
Original file line number Diff line number Diff line change
@@ -1,35 +1,42 @@
import { renderHook } from '@testing-library/react-hooks';
import { DropdownPlacements } from '../../../types';
import { PlacementDictionaryType } from '../../../types';
import { useDropdownStyleProps, UseDropdownStyleProps } from '../useDropdownStyleProps';

describe('useDropdownStyleProps', () => {
it('should return defaults', () => {
const props = { isOpen: true, placement: 'bottom-left' } as UseDropdownStyleProps;
const { result } = renderHook(() => useDropdownStyleProps(props));
const { result } = renderHook(() => useDropdownStyleProps());

expect(result.current.classProps.contentClassName).toBe('Dropdown is-open Dropdown--bottom Dropdown--left');
expect(result.current.classProps.contentClassName).toBe('Dropdown Dropdown--bottomLeft');
expect(result.current.classProps.wrapperClassName).toBe('DropdownWrapper');
});

it('should render as open', () => {
const props = {
isOpen: true,
} as UseDropdownStyleProps;
const { result } = renderHook(() => useDropdownStyleProps(props));

expect(result.current.classProps.contentClassName).toBe('Dropdown Dropdown--bottomLeft is-open');
expect(result.current.classProps.triggerClassName).toBe('is-expanded');
});

it('should transfer additional props', () => {
it('should change placement', () => {
const props = {
isOpen: false,
placement: DropdownPlacements.BOTTOM_LEFT,
transferProp: 'test',
};
placement: 'top-right' as PlacementDictionaryType,
} as UseDropdownStyleProps;
const { result } = renderHook(() => useDropdownStyleProps(props));

expect(result.current.props).toEqual({ transferProp: 'test' });
expect(result.current.classProps.contentClassName).toBe('Dropdown Dropdown--topRight');
});

it('should change placement class', () => {
it('should transfer additional props', () => {
const props = {
isOpen: false,
placement: DropdownPlacements.TOP_RIGHT,
};
transferProp: 'test',
} as UseDropdownStyleProps;
const { result } = renderHook(() => useDropdownStyleProps(props));

expect(result.current.classProps.contentClassName).toBe('Dropdown Dropdown--top Dropdown--right');
expect(result.current.classProps.contentClassName).toBe('Dropdown Dropdown--bottomLeft');
expect(result.current.props).toEqual({ transferProp: 'test' });
});
});
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { Ref } from 'react';
import { Markdown } from '@storybook/blocks';
import type { Meta, StoryObj } from '@storybook/react';

import { DropdownFullWidthModes, DropdownPlacements } from '../../../types';
import React, { Ref } from 'react';
import { Dropdown } from '..';
import { Button, Icon, Text } from '../..';
import { Placements } from '../../../constants';
import { DropdownFullWidthModes } from '../../../types';
import ReadMe from '../README.md';
import { Dropdown } from '..';

const meta: Meta<typeof Dropdown> = {
title: 'Components/Dropdown',
Expand Down Expand Up @@ -38,9 +38,9 @@ const meta: Meta<typeof Dropdown> = {
},
placement: {
control: 'select',
options: [...Object.values(DropdownPlacements), undefined],
options: Object.values(Placements),
table: {
defaultValue: { summary: DropdownPlacements.BOTTOM_LEFT },
defaultValue: { summary: Placements.BOTTOM_LEFT },
},
},
},
Expand All @@ -66,6 +66,7 @@ const meta: Meta<typeof Dropdown> = {
</>
),
id: 'DropdownExample',
placement: Placements.BOTTOM_LEFT,
},
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import classNames from 'classnames';
import { SpiritDropdownProps, DropdownProps, DropdownPlacements } from '../../types';
import { Placements } from '../../constants';
import { useClassNamePrefix } from '../../hooks';
import { DropdownProps, SpiritDropdownProps } from '../../types';
import { kebabCaseToCamelCase } from '../../utils';

export interface UseDropdownStyleProps extends SpiritDropdownProps {
/** open state */
Expand All @@ -19,24 +21,15 @@ export interface UseDropdownStylePropsReturn {
export const useDropdownStyleProps = (
props: UseDropdownStyleProps = { isOpen: false },
): UseDropdownStylePropsReturn => {
const { isOpen, placement = DropdownPlacements.BOTTOM_LEFT, ...modifiedProps } = props;
const { isOpen, placement = Placements.BOTTOM_LEFT, ...modifiedProps } = props;

const dropdownClass = useClassNamePrefix('Dropdown');
const dropdownWrapperClass = `${dropdownClass}Wrapper`;
const dropdownBottomClass = `${dropdownClass}--bottom`;
const dropdownTopClass = `${dropdownClass}--top`;
const dropdownLeftClass = `${dropdownClass}--left`;
const dropdownRightClass = `${dropdownClass}--right`;
const dropdownPlacementClass = `${dropdownClass}--${kebabCaseToCamelCase(placement)}`;
const expandedClass = isOpen ? 'is-expanded' : '';
const openClass = isOpen ? 'is-open' : '';
const dropdownPlacementClassNames = {
'bottom-left': classNames(dropdownBottomClass, dropdownLeftClass),
'bottom-right': classNames(dropdownBottomClass, dropdownRightClass),
'top-left': classNames(dropdownTopClass, dropdownLeftClass),
'top-right': classNames(dropdownTopClass, dropdownRightClass),
};
const dropdownClassName = classNames(dropdownClass, openClass, {
[dropdownPlacementClassNames[placement]]: placement,
});

const dropdownClassName = classNames(dropdownClass, dropdownPlacementClass, openClass);
const triggerClassName = classNames(expandedClass);

return {
Expand Down
38 changes: 20 additions & 18 deletions packages/web-react/src/components/Tooltip/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,27 +68,27 @@ const toggleHandler = () => setOpen(!open);

## Tooltip Props

| Name | Type | Default | Required | Description |
| ------------------ | ------------------------------------------------- | -------- | -------- | ----------------------------------------- |
| `children` | `ReactNode` | — | ✔ | Tooltip children's nodes |
| `closeLabel` | `string` | `Close` | ✕ | Tooltip label on close button |
| `isDismissible` | `bool` | — | ✕ | When it should appear with a close button |
| `onClose` | `(event: ClickEvent) => void` | — | ✕ | Close button callback |
| `open` | `bool` | — | ✕ | Tooltip open state control |
| `placement` | [`top` \| `right` \| `bottom` \| `left` \| `off`] | `bottom` | ✕ | Tooltip placement |
| `UNSAFE_className` | `string` | — | ✕ | Tooltip custom class name |
| `UNSAFE_style` | `CSSProperties` | — | ✕ | Tooltip custom style |
| Name | Type | Default | Required | Description |
| ------------------ | --------------------------------------------------- | -------- | -------- | ----------------------------------------- |
| `children` | `ReactNode` | — | ✔ | Tooltip children's nodes |
| `closeLabel` | `string` | `Close` | ✕ | Tooltip label on close button |
| `isDismissible` | `bool` | — | ✕ | When it should appear with a close button |
| `onClose` | `(event: ClickEvent) => void` | — | ✕ | Close button callback |
| `open` | `bool` | — | ✕ | Tooltip open state control |
| `placement` | [Placement dictionary][dictionary-placement], 'off' | `bottom` | ✕ | Tooltip placement |
| `UNSAFE_className` | `string` | — | ✕ | Tooltip custom class name |
| `UNSAFE_style` | `CSSProperties` | — | ✕ | Tooltip custom style |

## UncontrolledTooltip Props

| Name | Type | Default | Required | Description |
| ------------------ | ------------------------------------------------- | -------- | -------- | ----------------------------------------- |
| `children` | `ReactNode` | — | ✔ | Tooltip children's nodes |
| `closeLabel` | `string` | `Close` | ✕ | Tooltip label on close button |
| `isDismissible` | `bool` | — | ✕ | When it should appear with a close button |
| `placement` | [`top` \| `right` \| `bottom` \| `left` \| `off`] | `bottom` | ✕ | Tooltip placement |
| `UNSAFE_className` | `string` | — | ✕ | Tooltip custom class name |
| `UNSAFE_style` | `CSSProperties` | — | ✕ | Tooltip custom style |
| Name | Type | Default | Required | Description |
| ------------------ | --------------------------------------------------- | -------- | -------- | ----------------------------------------- |
| `children` | `ReactNode` | — | ✔ | Tooltip children's nodes |
| `closeLabel` | `string` | `Close` | ✕ | Tooltip label on close button |
| `isDismissible` | `bool` | — | ✕ | When it should appear with a close button |
| `placement` | [Placement dictionary][dictionary-placement], 'off' | `bottom` | ✕ | Tooltip placement |
| `UNSAFE_className` | `string` | — | ✕ | Tooltip custom class name |
| `UNSAFE_style` | `CSSProperties` | — | ✕ | Tooltip custom style |

## TooltipWrapper Props

Expand Down Expand Up @@ -219,3 +219,5 @@ const { getReferenceProps, getFloatingProps } = useInteractions([hover, focus, d
</Tooltip>
</div>;
```

[dictionary-placement]: https://github.com/lmc-eu/spirit-design-system/tree/main/docs/DICTIONARIES.md#placement
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import { renderHook } from '@testing-library/react-hooks';
import { useTooltipStyleProps } from '../useTooltipStyleProps';
import { PlacementDictionaryType } from '../../../types';
import { useTooltipStyleProps, UseTooltipStyleProps } from '../useTooltipStyleProps';

describe('useTooltipStyleProps', () => {
it('should return defaults', () => {
const { result } = renderHook(() => useTooltipStyleProps({}));

expect(result.current.classProps).toBeDefined();
expect(result.current.classProps.rootClassName).toBe('Tooltip Tooltip--bottom');
expect(result.current.classProps.wrapperClassName).toBe('TooltipWrapper');
expect(result.current.classProps.arrowClassName).toBe('Tooltip__arrow');
expect(result.current.classProps.closeButtonClassName).toBe('Tooltip__close');
});

it('should change placement', () => {
const props = {
placement: 'top-right' as PlacementDictionaryType,
} as UseTooltipStyleProps;
const { result } = renderHook(() => useTooltipStyleProps(props));

expect(result.current.classProps.rootClassName).toBe('Tooltip Tooltip--topRight');
});

it('should return dismissible class', () => {
const { result } = renderHook(() => useTooltipStyleProps({ isDismissible: true }));

expect(result.current.classProps).toBeDefined();
expect(result.current.classProps.rootClassName).toBe('Tooltip Tooltip--bottom Tooltip--dismissible');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import { ButtonLink } from '../../Button';
import TooltipWrapper from '../TooltipWrapper';
import Tooltip from '../Tooltip';

const TooltipOnHover = () => (
<>
<TooltipWrapper>
<ButtonLink href="#" aria-describedby="my-tooltip-hover-top" UNSAFE_className="TooltipTarget">
Tooltip on top
</ButtonLink>
<Tooltip id="my-tooltip-hover-top" placement="top">
Hello there!
</Tooltip>
</TooltipWrapper>

<TooltipWrapper>
<ButtonLink href="#" aria-describedby="my-tooltip-hover-right" UNSAFE_className="TooltipTarget">
Tooltip on right
</ButtonLink>
<Tooltip id="my-tooltip-hover-right" placement="right">
Hello there!
</Tooltip>
</TooltipWrapper>

<TooltipWrapper>
<ButtonLink href="#" aria-describedby="my-tooltip-hover-bottom" UNSAFE_className="TooltipTarget">
Tooltip on bottom
</ButtonLink>
<Tooltip id="my-tooltip-hover-bottom" placement="bottom">
Hello there!
</Tooltip>
</TooltipWrapper>

<TooltipWrapper>
<ButtonLink href="#" aria-describedby="my-tooltip-hover-left" UNSAFE_className="TooltipTarget">
Tooltip on left
</ButtonLink>
<Tooltip id="my-tooltip-hover-left" placement="left">
Hello there!
</Tooltip>
</TooltipWrapper>
</>
);

export default TooltipOnHover;
Loading