diff --git a/docs/content/SelectMenu.md b/docs/content/SelectMenu.md index 69ce40bf39f..f315a4d20e5 100644 --- a/docs/content/SelectMenu.md +++ b/docs/content/SelectMenu.md @@ -86,12 +86,36 @@ Used to wrap the content in a `SelectMenu`. ``` +### Right-aligned modal + +Use the `align='right'` prop to align the modal to the right. Note that this only aligns the modal contents, and not the SelectMenu itself. + +```jsx live + + + + + Projects + + Primer Components bugs + Primer Components roadmap + Project 3 + Project 4 + + + + +``` + ### System Props SelectMenu.Modal components get `COMMON` system props. Read our [System Props](/system-props) doc page for a full list of available props. ### Component Props -SelectMenu.Modal components do not get any additional props besides system props. + +| Prop name | Type | Default | Description | +| :-------- | :----- | :------ | ------------------------------------------------- | +| align | String | 'left' | Use `right` to align the select menu to the right | ## SelectMenu.List diff --git a/index.d.ts b/index.d.ts index d26531eff7d..9a163f33134 100644 --- a/index.d.ts +++ b/index.d.ts @@ -287,7 +287,9 @@ declare module '@primer/components' { initialTab?: string } - export interface SelectMenuModalProps extends CommonProps, Omit, 'color'> {} + export interface SelectMenuModalProps extends CommonProps, Omit, 'color'> { + align?: 'left' | 'right' + } export interface SelectMenuListProps extends CommonProps, Omit, 'color'> {} diff --git a/src/SelectMenu/SelectMenuModal.js b/src/SelectMenu/SelectMenuModal.js index fa8f937d47c..1767e6bf9a0 100644 --- a/src/SelectMenu/SelectMenuModal.js +++ b/src/SelectMenu/SelectMenuModal.js @@ -1,4 +1,5 @@ import React from 'react' +import PropTypes from 'prop-types' import styled, {keyframes, css} from 'styled-components' import {COMMON, get} from '../constants' import theme from '../theme' @@ -69,7 +70,7 @@ const modalWrapperStyles = css` @media (min-width: ${get('breakpoints.0')}) { position: absolute; top: auto; - right: auto; + right: ${props => (props.align === 'right' ? '0' : 'auto')}; bottom: auto; left: auto; padding: 0; @@ -95,10 +96,12 @@ const SelectMenuModal = ({children, theme, ...rest}) => { } SelectMenuModal.defaultProps = { + align: 'left', theme } - SelectMenuModal.propTypes = { + align: PropTypes.oneOf(['left', 'right']), + theme: PropTypes.object, ...COMMON.propTypes, ...sx.propTypes } diff --git a/src/__tests__/SelectMenu.js b/src/__tests__/SelectMenu.js index 93e5919e6f0..4699f5a5d9f 100644 --- a/src/__tests__/SelectMenu.js +++ b/src/__tests__/SelectMenu.js @@ -1,17 +1,17 @@ import React from 'react' import {SelectMenu, Button} from '..' -import {mount, renderRoot, COMPONENT_DISPLAY_NAME_REGEX, checkExports} from '../utils/testing' +import {mount, render, renderRoot, COMPONENT_DISPLAY_NAME_REGEX, checkExports} from '../utils/testing' import {COMMON} from '../constants' import {render as HTMLRender, cleanup} from '@testing-library/react' import {axe, toHaveNoViolations} from 'jest-axe' import 'babel-polyfill' expect.extend(toHaveNoViolations) -const BasicSelectMenu = ({onClick, as}) => { +const BasicSelectMenu = ({onClick, as, align = 'left'}) => { return ( - + Primer Components bugs @@ -128,4 +128,8 @@ describe('SelectMenu', () => { item.simulate('click') expect(component.getDOMNode().attributes.open).toBeFalsy() }) + + it('right-aligned modal has right: 0px', () => { + expect(render()).toMatchSnapshot() + }) }) diff --git a/src/__tests__/__snapshots__/SelectMenu.js.snap b/src/__tests__/__snapshots__/SelectMenu.js.snap new file mode 100644 index 00000000000..c8b29fbfa91 --- /dev/null +++ b/src/__tests__/__snapshots__/SelectMenu.js.snap @@ -0,0 +1,481 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SelectMenu right-aligned modal has right: 0px 1`] = ` +.c1 { + position: relative; + display: inline-block; + padding: 6px 16px; + font-weight: 600; + line-height: 20px; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + border-radius: 6px; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + -webkit-text-decoration: none; + text-decoration: none; + text-align: center; + font-size: 14px; + color: #24292e; + background-color: #fafbfc; + border: 1px solid rgba(27,31,35,0.12); + box-shadow: 0px 1px 0px rgba(27,31,35,0.04),inset 0px 2px 0px rgba(255,255,255,0.25); +} + +.c1:hover { + -webkit-text-decoration: none; + text-decoration: none; +} + +.c1:focus { + outline: none; +} + +.c1:disabled { + cursor: default; +} + +.c1:disabled svg { + opacity: 0.6; +} + +.c1:hover { + background-color: #F3F4F6; + box-shadow: 0px 1px 0px rgba(209,213,218,0.2),inset 0px 2px 0px rgba(255,255,255,0.1); +} + +.c1:focus { + border-color: transparent; + box-shadow: 0 0 0 3px rgba(3,102,214,0.3); +} + +.c1:active { + background-color: #edeff2; + box-shadow: inset 0px 2px 0px rgba(149,157,165,0.1); + border-color: #d1d5da; +} + +.c1:disabled { + color: #959da5; + background-color: #fafbfc; + border-color: #eaecef; +} + +.c6 { + padding: 4px 16px; + margin: 0; + font-size: 12px; + font-weight: 600; + color: #6a737d; + background-color: #f6f8fa; + border-bottom: 1px solid #eaecef; +} + +.c7 { + margin-top: -1px; + padding: 8px 16px; + font-size: 12px; + color: #6a737d; + text-align: center; + border-top: 1px solid #e1e4e8; +} + +.c5 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding: 16px; + overflow: hidden; + text-align: left; + cursor: pointer; + background-color: #fff; + border: 0; + border-bottom: 1px solid #eaecef; + color: #586069; + -webkit-text-decoration: none; + text-decoration: none; + font-size: 12px; + width: 100%; +} + +.c5:hover { + -webkit-text-decoration: none; + text-decoration: none; +} + +.c5:focus { + outline: none; +} + +.c5[hidden] { + display: none !important; +} + +.c5 .SelectMenu-icon { + width: 16px; + margin-right: 8px; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; +} + +.c5 .SelectMenu-selected-icon { + visibility: hidden; + -webkit-transition: -webkit-transform 0.12s cubic-bezier(0.5,0.1,1,0.5),visibility 0s 0.12s linear; + -webkit-transition: transform 0.12s cubic-bezier(0.5,0.1,1,0.5),visibility 0s 0.12s linear; + transition: transform 0.12s cubic-bezier(0.5,0.1,1,0.5),visibility 0s 0.12s linear; + -webkit-transform: scale(0); + -ms-transform: scale(0); + transform: scale(0); +} + +.c5[aria-checked='true'] { + font-weight: 500; + color: #24292e; +} + +.c5[aria-checked='true'] .SelectMenu-selected-icon { + visibility: visible; + -webkit-transition: -webkit-transform 0.12s cubic-bezier(0,0,0.2,1),visibility 0s linear; + -webkit-transition: transform 0.12s cubic-bezier(0,0,0.2,1),visibility 0s linear; + transition: transform 0.12s cubic-bezier(0,0,0.2,1),visibility 0s linear; + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); +} + +.c4 { + position: relative; + padding: 0; + margin: 0; + -webkit-flex: auto; + -ms-flex: auto; + flex: auto; + overflow-x: hidden; + overflow-y: auto; + background-color: #fff; + -webkit-overflow-scrolling: touch; +} + +.c3 { + position: relative; + z-index: 99; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + max-height: 66%; + margin: auto 0; + overflow: hidden; + pointer-events: auto; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + background-color: #fff; + border-radius: 6px; + box-shadow: 0 1px 5px rgba(27,31,35,0.15); + -webkit-animation: lejQAW 0.12s cubic-bezier(0,0.1,0.1,1) backwards; + animation: lejQAW 0.12s cubic-bezier(0,0.1,0.1,1) backwards; +} + +.c2 { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 99; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 16px; + pointer-events: none; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; +} + +.c2::before { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + content: ''; + background-color: rgba(27,31,35,0.5); +} + +.c0[open] > summary::before { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 80; + display: block; + cursor: default; + content: ' '; + background: transparent; +} + +.c0 > summary { + list-style: none; +} + +.c0 > summary::before { + display: none; +} + +.c0 > summary::-webkit-details-marker { + display: none; +} + +@media (min-width:544px) { + .c7 { + padding: 4px 8px; + } +} + +@media (min-width:544px) { + .c5 { + padding-top: 8px; + padding-bottom: 8px; + } +} + +@media (hover:hover) { + .c5 body:not(.intent-mouse) .SelectMenu-item:focus, + .c5:hover, + .c5:active, + .c5:focus { + background-color: #f6f8fa; + } +} + +@media (hover:none) { + .c5 { + -webkit-tap-highlight-color: rgba(#d1d5da,0.5); + } + + .c5:focus, + .c5:active { + background-color: #fafbfc; + } +} + +@media (hover:hover) { + .c4 .SelectMenuTab:focus { + background-color: #dbedff; + } + + .c4 .SelectMenuTab:not([aria-checked='true']):hover { + color: #24292e; + background-color: #e1e4e8; + } + + .c4 .SelectMenuTab:not([aria-checked='true']):active { + color: #24292e; + background-color: #f6f8fa; + } +} + +@media (min-width:544px) { + .c3 { + width: '300px'; + height: auto; + max-height: 350px; + margin: 4px 0 16px 0; + font-size: 12px; + border: 1px solid #d1d5da; + border-radius: 6px; + box-shadow: 0 1px 5px rgba(27,31,35,0.15) !default; + } +} + +@media (min-width:544px) { + .c2::before { + display: none; + } +} + +@media (min-width:544px) { + .c2 { + position: absolute; + top: auto; + right: 0; + bottom: auto; + left: auto; + padding: 0; + } +} + +
+ + Projects + + +
+`;