Skip to content

fix(menu): prop to display menu checkbox #151

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

Merged
merged 6 commits into from
Jul 7, 2022
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
13 changes: 13 additions & 0 deletions .changeset/stupid-rockets-clap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@cube-dev/ui-kit": patch
---

Adds prop `selectionType` for `Menu` component. That stands for values `checkbox` or `radio`.

```jsx
<Menu selectionType="checkbox" selectionMode="single">
<Item key="1">Item 1</Item>
<Item key="2">Item 2</Item>
</Menu>
```

30 changes: 30 additions & 0 deletions src/components/pickers/Menu/Menu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,36 @@ export const MenuSelectableMultiple = (props) => {
});
};

export const MenuSelectableCheckboxes = (props) => {
const [selectedKeys, setSelectedKeys] = useState(['1', '2']);
const onSelectionChange = (key) => {
setSelectedKeys(key);
};

return MenuTemplate({
...props,
selectionIcon: 'checkbox',
selectionMode: 'multiple',
selectedKeys,
onSelectionChange,
});
};

export const MenuSelectableRadio = (props) => {
const [selectedKeys, setSelectedKeys] = useState(['1']);
const onSelectionChange = (key) => {
setSelectedKeys(key);
};

return MenuTemplate({
...props,
selectionIcon: 'radio',
selectionMode: 'single',
selectedKeys,
onSelectionChange,
});
};

export const PaymentDetails = (props) => {
return (
<Root>
Expand Down
7 changes: 5 additions & 2 deletions src/components/pickers/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ import {
import { StyledMenu, StyledMenuHeader } from './styled';
import { MenuItem } from './MenuItem';
import { MenuSection } from './MenuSection';
import { MenuButtonProps } from './MenuButton';
import { MenuButtonProps, MenuSelectionType } from './MenuButton';
import { useMenuContext } from './context';

export interface CubeMenuProps<T>
extends ContainerStyleProps,
AriaMenuProps<T> {
selectionIcon?: MenuSelectionType;
header?: ReactNode;
footer?: ReactNode;
styles?: Styles;
Expand All @@ -34,7 +35,7 @@ function Menu<T extends object>(
props: CubeMenuProps<T>,
ref: DOMRef<HTMLUListElement>,
) {
const { header, footer } = props;
const { header, footer, selectionIcon } = props;
const domRef = useDOMRef(ref);
const contextProps = useMenuContext();
const completeProps = mergeProps(contextProps, props);
Expand Down Expand Up @@ -70,6 +71,7 @@ function Menu<T extends object>(
key={item.key}
item={item}
state={state}
selectionIcon={selectionIcon}
onAction={completeProps.onAction}
/>
);
Expand All @@ -80,6 +82,7 @@ function Menu<T extends object>(
key={item.key}
item={item}
state={state}
selectionIcon={selectionIcon}
onAction={completeProps.onAction}
/>
);
Expand Down
43 changes: 39 additions & 4 deletions src/components/pickers/Menu/MenuButton.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ReactNode } from 'react';
import { Button, CubeButtonProps } from '../../actions';
import { Text } from '../../content/Text';
import { Styles } from '../../../tasty';
import { Styles, tasty } from '../../../tasty';
import { Space } from '../../layout/Space';
import { CheckOutlined } from '@ant-design/icons';
import { CheckOutlined, CheckCircleOutlined } from '@ant-design/icons';

const ACTION_BUTTON: Styles = {
border: {
Expand All @@ -30,6 +30,8 @@ const ACTION_BUTTON: Styles = {
padding: {
'': '(0.75x - 1px) (1.5x - 1px)',
'selectable & !selected':
'(0.75x - 1px) (1.5x - 1px) (0.75x - 1px) (1.5x - 1px)',
'selectionIcon & selectable & !selected':
'(0.75x - 1px) (1.5x - 1px) (0.75x - 1px) (1.5x - 1px + 22px)',
},
display: 'flex',
Expand All @@ -45,6 +47,23 @@ const ACTION_BUTTON: Styles = {
},
};

const RadioIcon = tasty({
styles: {
display: 'flex',
width: '1.875x',
placeContent: 'center',

'&::before': {
display: 'block',
content: '""',
width: '1x',
height: '1x',
radius: 'round',
fill: '#current',
},
},
});

const getPostfix = (postfix) =>
typeof postfix === 'string' ? (
<Text nowrap color="inherit" data-element="Postfix">
Expand All @@ -54,22 +73,38 @@ const getPostfix = (postfix) =>
postfix
);

export type MenuSelectionType = 'checkbox' | 'radio';

export type MenuButtonProps = {
postfix: ReactNode;
selectionIcon?: MenuSelectionType;
isSelectable?: boolean;
disabled?: boolean;
} & CubeButtonProps;

const getSelectionTypeIcon = (selectionIcon?: MenuSelectionType) => {
switch (selectionIcon) {
case 'checkbox':
return <CheckOutlined />;
case 'radio':
return <RadioIcon />;
default:
return null;
}
};

export function MenuButton({
children,
icon,
postfix,
...props
}: MenuButtonProps) {
const { isSelected, isSelectable } = props;
const checkIcon = isSelectable && isSelected ? <CheckOutlined /> : null;
const { selectionIcon, isSelected, isSelectable } = props;
const checkIcon =
isSelectable && isSelected ? getSelectionTypeIcon(selectionIcon) : null;
const mods = {
...props.mods,
selectionIcon: !!selectionIcon,
selectable: isSelectable,
selected: isSelected,
};
Expand Down
6 changes: 4 additions & 2 deletions src/components/pickers/Menu/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@ import { useMenuItem } from '@react-aria/menu';
import { mergeProps, ClearSlots, SlotProvider } from '../../../utils/react';
import { useMenuContext } from './context';
import { StyledMenuItem } from './styled';
import { MenuButton } from './MenuButton';
import { MenuButton, MenuSelectionType } from './MenuButton';

export interface MenuItemProps<T> {
item: Node<T>;
state: TreeState<T>;
selectionIcon?: MenuSelectionType;
isVirtualized?: boolean;
onAction?: (key: Key) => void;
}

/** @private */
export function MenuItem<T>(props: MenuItemProps<T>) {
const { item, state, isVirtualized, onAction } = props;
const { item, state, selectionIcon, isVirtualized, onAction } = props;
const { onClose, closeOnSelect } = useMenuContext();
const { rendered, key, props: itemProps } = item;

Expand Down Expand Up @@ -48,6 +49,7 @@ export function MenuItem<T>(props: MenuItemProps<T>) {
typeof rendered === 'string' ? (
<MenuButton
{...itemProps}
selectionIcon={selectionIcon}
isSelectable={isSelectable}
isSelected={isSelected}
isDisabled={isDisabled}
Expand Down