diff --git a/CHANGELOG.md b/CHANGELOG.md index d19a28ab38..da25f1c2d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add accessibility behavior description @kolaps33 ([#74](https://github.com/stardust-ui/react/pull/74)) - Add strict null checks for generated TS types @smykhailov ([#108](https://github.com/stardust-ui/react/pull/108)) - Export themes at `@stardust-ui/react/themes` @levithomason ([#145](https://github.com/stardust-ui/react/pull/145)) +- Add support for Menu `vertical pointing` prop @miroslavstastny ([#123](https://github.com/stardust-ui/react/pull/123)) ### Documentation - Add a Quick Start guide @levithomason ([#145](https://github.com/stardust-ui/react/pull/145)) diff --git a/docs/src/examples/components/Menu/Variations/MenuExamplePointingStart.shorthand.tsx b/docs/src/examples/components/Menu/Variations/MenuExamplePointingStart.shorthand.tsx new file mode 100644 index 0000000000..a24680cea7 --- /dev/null +++ b/docs/src/examples/components/Menu/Variations/MenuExamplePointingStart.shorthand.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import { Menu } from '@stardust-ui/react' + +const items = [ + { key: 'editorials', content: 'Editorials' }, + { key: 'review', content: 'Reviews' }, + { key: 'events', content: 'Upcoming Events' }, +] + +const MenuExamplePointingStart = () => ( + +) + +export default MenuExamplePointingStart diff --git a/docs/src/examples/components/Menu/Variations/MenuExamplePointingStartPrimary.shorthand.tsx b/docs/src/examples/components/Menu/Variations/MenuExamplePointingStartPrimary.shorthand.tsx new file mode 100644 index 0000000000..c01ec542f5 --- /dev/null +++ b/docs/src/examples/components/Menu/Variations/MenuExamplePointingStartPrimary.shorthand.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import { Menu } from '@stardust-ui/react' + +const items = [ + { key: 'editorials', content: 'Editorials' }, + { key: 'review', content: 'Reviews' }, + { key: 'events', content: 'Upcoming Events' }, +] + +const MenuExamplePointingStartPrimary = () => ( + +) + +export default MenuExamplePointingStartPrimary diff --git a/docs/src/examples/components/Menu/Variations/MenuExampleVerticalPointing.shorthand.tsx b/docs/src/examples/components/Menu/Variations/MenuExampleVerticalPointing.shorthand.tsx new file mode 100644 index 0000000000..a9bf96ca97 --- /dev/null +++ b/docs/src/examples/components/Menu/Variations/MenuExampleVerticalPointing.shorthand.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import { Menu } from '@stardust-ui/react' + +const items = [ + { key: 'editorials', content: 'Editorials' }, + { key: 'review', content: 'Reviews' }, + { key: 'events', content: 'Upcoming Events' }, +] + +const MenuExampleVerticalPointing = () => ( + +) + +export default MenuExampleVerticalPointing diff --git a/docs/src/examples/components/Menu/Variations/MenuExampleVerticalPointingEnd.shorthand.tsx b/docs/src/examples/components/Menu/Variations/MenuExampleVerticalPointingEnd.shorthand.tsx new file mode 100644 index 0000000000..090b26e170 --- /dev/null +++ b/docs/src/examples/components/Menu/Variations/MenuExampleVerticalPointingEnd.shorthand.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import { Menu } from '@stardust-ui/react' + +const items = [ + { key: 'editorials', content: 'Editorials' }, + { key: 'review', content: 'Reviews' }, + { key: 'events', content: 'Upcoming Events' }, +] + +const MenuExampleVerticalPointingEnd = () => ( + +) + +export default MenuExampleVerticalPointingEnd diff --git a/docs/src/examples/components/Menu/Variations/index.tsx b/docs/src/examples/components/Menu/Variations/index.tsx index 9aa04b73fe..7cb269c989 100644 --- a/docs/src/examples/components/Menu/Variations/index.tsx +++ b/docs/src/examples/components/Menu/Variations/index.tsx @@ -34,6 +34,26 @@ const Variations = () => ( description="A menu can point to show its relationship to nearby content." examplePath="components/Menu/Variations/MenuExamplePointingPrimary" /> + + + + { /** A menu can adjust its appearance to de-emphasize its contents. */ pills: PropTypes.bool, - /** A menu can point to show its relationship to nearby content. */ - pointing: PropTypes.bool, + /** + * A menu can point to show its relationship to nearby content. + * For vertical menu, it can point to the start of the item or to the end. + */ + pointing: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['start', 'end'])]), /** The menu can have primary or secondary type */ type: PropTypes.oneOf(['primary', 'secondary']), diff --git a/src/components/Menu/MenuItem.tsx b/src/components/Menu/MenuItem.tsx index dc39e76a0f..a388662f32 100644 --- a/src/components/Menu/MenuItem.tsx +++ b/src/components/Menu/MenuItem.tsx @@ -52,8 +52,11 @@ class MenuItem extends UIComponent { /** A menu can adjust its appearance to de-emphasize its contents. */ pills: PropTypes.bool, - /** A menu can point to show its relationship to nearby content. */ - pointing: PropTypes.bool, + /** + * A menu can point to show its relationship to nearby content. + * For vertical menu, it can point to the start of the item or to the end. + */ + pointing: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['start', 'end'])]), /** The menu can have primary or secondary type */ type: PropTypes.oneOf(['primary', 'secondary']), diff --git a/src/themes/teams/components/Menu/menuItemStyles.ts b/src/themes/teams/components/Menu/menuItemStyles.ts index 813e4081cd..0288152536 100644 --- a/src/themes/teams/components/Menu/menuItemStyles.ts +++ b/src/themes/teams/components/Menu/menuItemStyles.ts @@ -8,10 +8,11 @@ const underlinedItem = (color): ICSSInJSStyle => ({ }) const itemSeparator = ({ props, variables }: { props: any; variables }): ICSSInJSStyle => { - const { active, iconOnly, pills, type, underlined, vertical } = props + const { active, iconOnly, pointing, pills, type, underlined, vertical } = props return { ...(!pills && !underlined && + !(pointing && vertical) && !iconOnly && { '::before': { position: 'absolute', @@ -35,6 +36,56 @@ const itemSeparator = ({ props, variables }: { props: any; variables }): ICSSInJ } } +const pointingBeak = ({ props, variables }: { props: any; variables }): ICSSInJSStyle => { + const { pointing, type } = props + + let backgroundColor: string + let borderColor: string + let top: string + let borders: ICSSInJSStyle + + if (type === 'primary') { + backgroundColor = variables.typePrimaryActiveBackgroundColor + borderColor = variables.typePrimaryBorderColor + } else { + backgroundColor = variables.defaultActiveBackgroundColor + borderColor = variables.defaultBorderColor + } + + if (pointing === 'start') { + borders = { + borderTop: `1px solid ${borderColor}`, + borderLeft: `1px solid ${borderColor}`, + } + top = '-1px' // 1px for the border + } else { + borders = { + borderBottom: `1px solid ${borderColor}`, + borderRight: `1px solid ${borderColor}`, + } + top = '100%' + } + + return { + '::after': { + visibility: 'visible', + background: backgroundColor, + position: 'absolute', + content: '""', + top, + left: '50%', + transform: 'translateX(-50%) translateY(-50%) rotate(45deg)', + margin: '.5px 0 0', + width: pxToRem(10), + height: pxToRem(10), + border: 'none', + ...borders, + zIndex: 2, + transition: 'background .1s ease', + }, + } +} + const menuItemStyles = { root: ({ props, variables }: { props: any; variables: IMenuVariables }): ICSSInJSStyle => { const { active, iconOnly, pills, pointing, type, underlined, vertical } = props @@ -67,6 +118,16 @@ const menuItemStyles = { color: variables.defaultColor, }), ...itemSeparator({ props, variables }), + ...(pointing && + vertical && { + border: '1px solid transparent', + borderTopLeftRadius: `${pxToRem(3)}`, + borderTopRightRadius: `${pxToRem(3)}`, + ...(pointing === 'end' + ? { borderRight: `${pxToRem(3)} solid transparent` } + : { borderLeft: `${pxToRem(3)} solid transparent` }), + marginBottom: `${pxToRem(12)}`, + }), ':hover': { color: variables.defaultActiveColor, @@ -96,36 +157,19 @@ const menuItemStyles = { }), }), }, - ...(pointing && { - '::after': { - visibility: 'visible', - background: variables.defaultActiveBackgroundColor, - position: 'absolute', - content: '""', - top: '100%', - left: '50%', - transform: 'translateX(-50%) translateY(-50%) rotate(45deg)', - margin: '.5px 0 0', - width: pxToRem(10), - height: pxToRem(10), - border: 'none', - borderBottom: `1px solid ${variables.defaultBorderColor}`, - borderRight: `1px solid ${variables.defaultBorderColor}`, - zIndex: 2, - transition: 'background .1s ease', - ...(type === 'primary' && { - background: variables.typePrimaryActiveBackgroundColor, - borderBottom: `1px solid ${variables.typePrimaryBorderColor}`, - borderRight: `1px solid ${variables.typePrimaryBorderColor}`, - }), - }, - }), + ...(pointing && !vertical && pointingBeak({ props, variables })), + ...(pointing && + vertical && { + ...(pointing === 'end' + ? { borderRight: `${pxToRem(3)} solid ${variables.typePrimaryActiveColor}` } + : { borderLeft: `${pxToRem(3)} solid ${variables.typePrimaryActiveColor}` }), + }), }), } }, anchor: ({ props, variables }): ICSSInJSStyle => { - const { active, iconOnly, type, underlined } = props + const { active, iconOnly, pointing, type, underlined, vertical } = props const { iconsMenuItemSize } = variables return { @@ -133,7 +177,9 @@ const menuItemStyles = { display: 'block', ...(underlined ? { padding: `0 0 ${pxToRem(8)} 0` } - : { padding: `${pxToRem(14)} ${pxToRem(18)}` }), + : pointing && vertical + ? { padding: `${pxToRem(8)} ${pxToRem(18)}` } + : { padding: `${pxToRem(14)} ${pxToRem(18)}` }), cursor: 'pointer', ...(iconOnly && { diff --git a/src/themes/teams/components/Menu/menuStyles.ts b/src/themes/teams/components/Menu/menuStyles.ts index 67558a2eed..e003ffc23c 100644 --- a/src/themes/teams/components/Menu/menuStyles.ts +++ b/src/themes/teams/components/Menu/menuStyles.ts @@ -7,7 +7,7 @@ const solidBorder = (color: string) => ({ export default { root: ({ props, variables }): ICSSInJSStyle => { - const { iconOnly, fluid, pills, type, underlined, vertical } = props + const { iconOnly, fluid, pointing, pills, type, underlined, vertical } = props return { display: 'flex', ...(vertical && { @@ -20,6 +20,7 @@ export default { }), ...(!pills && !iconOnly && + !(pointing && vertical) && !underlined && { ...solidBorder(variables.defaultBorderColor), ...(type === 'primary' && {