Skip to content

Commit

Permalink
feat(fuselage): Extend menu options props (#650)
Browse files Browse the repository at this point in the history
* feat: extend menu options props

* Rearrange stories

* Handle option types on `useCursor`

* Prefer optional call

* Keep `MenuProps` not exported

* Prefer `ReactNode`

* chore: duplicated menuOptions

* Revert "chore: duplicated menuOptions"

This reverts commit b0cd0b9.

Co-authored-by: Tasso Evangelista <[email protected]>
  • Loading branch information
dougfabris and tassoevan authored Feb 18, 2022
1 parent b3b075b commit 5193110
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 62 deletions.
61 changes: 57 additions & 4 deletions packages/fuselage/src/components/Menu/Menu.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { action } from '@storybook/addon-actions';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import React from 'react';

import { Box, Menu } from '..';
import { menuOptions } from '../../../.storybook/helpers.js';
import { Icon } from '../Icon';

export default {
title: 'Navigation/Menu',
Expand All @@ -13,11 +14,63 @@ export default {
component: 'Kebab Menu',
},
},
layout: 'centered',
},
} as ComponentMeta<typeof Menu>;

export const Template: ComponentStory<typeof Menu> = () => (
<Box style={{ position: 'relative', maxWidth: 250 }}>
<Menu options={menuOptions} />
const Template: ComponentStory<typeof Menu> = (args) => (
<Box position='relative' maxWidth={250}>
<Menu {...args} />
</Box>
);

export const simple = Template.bind({});
simple.args = {
options: {
makeAdmin: {
label: (
<Box display='flex' alignItems='center'>
<Icon mie='x4' name='key' size='x16' />
Make Admin
</Box>
),
action: action('makeAdmin.action'),
},
delete: {
label: (
<Box display='flex' alignItems='center' color='danger'>
<Icon mie='x4' name='trash' size='x16' />
Delete
</Box>
),
action: action('delete.action'),
},
},
};

export const complex = Template.bind({});
complex.args = {
options: {
example: {
label: 'Example',
action: action('example.action'),
},
divider1: {
type: 'divider',
},
heading: {
type: 'heading',
label: 'Heading Example',
},
delete: {
type: 'option',
label: (
<Box display='flex' alignItems='center' color='danger'>
<Icon mie='x4' name='trash' size='x16' />
Delete
</Box>
),
action: action('delete.action'),
},
},
};
21 changes: 14 additions & 7 deletions packages/fuselage/src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ import React, {
useRef,
useCallback,
ComponentProps,
ReactElement,
ElementType,
ReactNode,
} from 'react';

import { ActionButton, PositionAnimated, Options, useCursor, Box } from '..';
import type { OptionType } from '../Options';

export type MenuProps = Omit<ComponentProps<typeof ActionButton>, 'icon'> & {
type MenuProps = Omit<ComponentProps<typeof ActionButton>, 'icon'> & {
options: {
[id: string]: {
label: ReactElement | string;
action: () => void;
type?: 'option' | 'heading' | 'divider';
label?: ReactNode;
action?: () => void;
};
};
optionWidth?: ComponentProps<typeof Box>['width'];
Expand All @@ -24,11 +25,16 @@ export type MenuProps = Omit<ComponentProps<typeof ActionButton>, 'icon'> & {
};

const menuAction = ([selected]: OptionType, options: MenuProps['options']) => {
options[selected].action();
options[selected].action?.();
};

const mapOptions = (options: MenuProps['options']): OptionType[] =>
Object.entries(options).map(([value, { label }]) => [value, label]);
Object.entries(options).map(([value, { type = 'option', label }]) => [
value,
label,
undefined,
type,
]);

export const Menu = ({
tiny,
Expand Down Expand Up @@ -61,7 +67,7 @@ export const Menu = ({
show();
ref.current.classList.add('focus-visible');
}
}, [show]);
}, [hide, show]);

const handleSelection = useCallback(
(args) => {
Expand All @@ -71,6 +77,7 @@ export const Menu = ({
},
[hide, reset, options]
);

return (
<>
<ActionButton
Expand Down
17 changes: 2 additions & 15 deletions packages/fuselage/src/components/Options/Options.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { ComponentStory, ComponentMeta } from '@storybook/react';
import React, { createRef } from 'react';

import { Options, OptionType } from '.';
import { Box, Menu, Divider } from '..';
import { Box, Menu } from '..';
import { CheckOption } from './CheckOption';
import Option, { OptionHeader } from './Option';
import Option from './Option';

const options: OptionType[] = [
[1, 'a teste 1'],
Expand Down Expand Up @@ -87,16 +87,3 @@ CustomEmpty.args = {
options: [],
customEmpty: 'Custom empty placeholder',
};

export const CustomRender: ComponentStory<typeof Options> = (args) => (
<Box position='relative' maxWidth={250}>
<Options {...args} ref={createRef()}>
<Option>Option Example</Option>
<Divider />
<OptionHeader>Title</OptionHeader>
<CheckOption icon='magnifier'>CheckOption Example</CheckOption>
<CheckOption>CheckOption Example</CheckOption>
<CheckOption>CheckOption Example With Ellipsis</CheckOption>
</Options>
</Box>
);
59 changes: 35 additions & 24 deletions packages/fuselage/src/components/Options/Options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import React, {
import { Box } from '../Box';
import Scrollable from '../Scrollable';
import Tile from '../Tile';
import Option from './Option';
import Option, { OptionHeader, OptionDivider } from './Option';
import { useCursor } from './useCursor';

export { useCursor };
Expand All @@ -24,11 +24,16 @@ const prevent = (e: SyntheticEvent) => {
e.stopPropagation();
};

export type OptionType = [string | number, ReactNode, boolean?];
export type OptionType = [
value: string | number,
label: ReactNode,
selected?: boolean,
type?: 'heading' | 'divider' | 'option'
];

type OptionsProps = Omit<ComponentProps<typeof Box>, 'onSelect'> & {
multiple?: boolean;
options: Array<OptionType>;
options: OptionType[];
cursor: number;
renderItem?: ElementType;
renderEmpty?: ElementType;
Expand All @@ -51,7 +56,6 @@ export const Options = forwardRef(
renderItem: OptionComponent = Option,
onSelect,
customEmpty,
children,
...props
}: OptionsProps,
ref: Ref<HTMLElement>
Expand All @@ -78,22 +82,31 @@ export const Options = forwardRef(

const optionsMemoized = useMemo(
() =>
options?.map(([value, label, selected], i) => (
<OptionComponent
role='option'
label={label}
onMouseDown={(e: SyntheticEvent) => {
prevent(e);
onSelect([value, label]);
return false;
}}
key={value}
value={value}
selected={selected || (multiple !== true && null)}
focus={cursor === i || null}
/>
)),
[options, multiple, cursor, onSelect]
options?.map(([value, label, selected, type], i) => {
switch (type) {
case 'heading':
return <OptionHeader key={value}>{label}</OptionHeader>;
case 'divider':
return <OptionDivider key={value} />;
default:
return (
<OptionComponent
role='option'
label={label}
onMouseDown={(e: SyntheticEvent) => {
prevent(e);
onSelect([value, label]);
return false;
}}
key={value}
value={value}
selected={selected || (multiple !== true && null)}
focus={cursor === i || null}
/>
);
}
}),
[options, multiple, cursor, onSelect, OptionComponent]
);

return (
Expand All @@ -116,10 +129,8 @@ export const Options = forwardRef(
: undefined
}
>
{options?.length ? optionsMemoized : children}
{!options?.length && !children && (
<EmptyComponent customEmpty={customEmpty} />
)}
{!options.length && <EmptyComponent customEmpty={customEmpty} />}
{optionsMemoized}
</Tile>
</Scrollable>
</Tile>
Expand Down
4 changes: 2 additions & 2 deletions packages/fuselage/src/components/Options/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ $variants: (
opacity: 0;
}

&.rcx-option__column {
.rcx-option__column {
@extend %column;
display: flex;

Expand All @@ -91,7 +91,7 @@ $variants: (
min-height: lengths.size(20);
}

&.rcx-option__description {
.rcx-option__description {
@include typography.use-font-scale(p2);
@extend %column;
display: inline;
Expand Down
Loading

0 comments on commit 5193110

Please sign in to comment.