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

Create Selectable component #660

Merged
merged 32 commits into from
Dec 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d542ff4
Create Selectable component
Oct 8, 2020
fbeaf6a
PR feedback and adjustments
Oct 22, 2020
33c35a8
Merge branch 'master' of https://github.com/mongodb/leafygreen-ui int…
Oct 28, 2020
0198460
Merge branch 'master' of https://github.com/mongodb/leafygreen-ui int…
Nov 4, 2020
69d3f72
More code reuse
Nov 5, 2020
53a053a
prettier
Nov 6, 2020
f4c1383
updated design
Nov 6, 2020
589bcce
Merge branch 'master' of https://github.com/mongodb/leafygreen-ui int…
Nov 6, 2020
e4a1b1e
update readme
Nov 9, 2020
243b8f4
Merge branch 'master' of https://github.com/mongodb/leafygreen-ui int…
Nov 9, 2020
c894749
use button as input
Nov 9, 2020
71ff733
Delegate focus to button
Nov 10, 2020
102df24
Merge branch 'master' of https://github.com/mongodb/leafygreen-ui int…
Nov 19, 2020
dec82f6
cleanup
Nov 30, 2020
bff1cb5
Merge branch 'master' of https://github.com/mongodb/leafygreen-ui int…
Nov 30, 2020
a505370
Merge branch 'master' of https://github.com/mongodb/leafygreen-ui int…
Nov 30, 2020
15465ac
use updated button
Dec 1, 2020
d718a9f
revert eslintignore
Dec 1, 2020
00365e1
Make name optional
Dec 1, 2020
cc9cb4d
Merge branch 'master' into PD-271-Select
lazytype Dec 3, 2020
3d7dd24
Merge branch 'master' of https://github.com/mongodb/leafygreen-ui int…
Dec 7, 2020
1e0e746
PR feedback
Dec 7, 2020
ced5939
Merge branch 'master' of https://github.com/mongodb/leafygreen-ui int…
Dec 7, 2020
ee90ed2
Merge branch 'PD-271-Select' of https://github.com/mongodb/leafygreen…
Dec 7, 2020
354f282
fix readme
Dec 7, 2020
c483486
prettier
Dec 7, 2020
ba040c3
supply darkMode prop to Buton
Dec 8, 2020
a391a30
fix scroll
Dec 9, 2020
ccc0700
prettier
Dec 9, 2020
4309c3b
Give option group aria label and hide caret icon
lazytype Dec 10, 2020
bc7a84e
Merge branch 'master' into PD-271-Select
lazytype Dec 17, 2020
3713a96
Merge branch 'master' into PD-271-Select
lazytype Dec 17, 2020
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
5 changes: 5 additions & 0 deletions .changeset/few-bugs-promise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@leafygreen-ui/select': major
---

Initial release of Select component
7 changes: 7 additions & 0 deletions .changeset/wet-hotels-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@leafygreen-ui/button': minor
---

- The default outline shown by the browser is no longer shown when a button is focused. Instead, a custom interaction ring will be displayed that conforms to LeafyGreen-UI style guidelines whenever the button is hovered or focused.
- Button now has a `darkMode` prop which for now only controls the interaction ring color.
- Button now has a `focused` prop which can be used to force the button to display as focused or unfocused.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ A set of CSS styles and React components built with design in mind.
- [Portal](https://github.com/mongodb/leafygreen-ui/tree/master/packages/portal)
- [Radio Box Group](https://github.com/mongodb/leafygreen-ui/tree/master/packages/radio-box-group)
- [Radio Group](https://github.com/mongodb/leafygreen-ui/tree/master/packages/radio-group)
- [Select](https://github.com/mongodb/leafygreen-ui/tree/master/packages/select)
- [Side Nav](https://github.com/mongodb/leafygreen-ui/tree/master/packages/side-nav)
- [Stepper](https://github.com/mongodb/leafygreen-ui/tree/master/packages/stepper)
- [Syntax](https://github.com/mongodb/leafygreen-ui/tree/master/packages/syntax)
Expand Down
1 change: 1 addition & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module.exports = function (api) {
'@babel/plugin-proposal-export-default-from',
'@babel/plugin-proposal-optional-chaining',
'@babel/plugin-proposal-nullish-coalescing-operator',
'@babel/plugin-proposal-logical-assignment-operators',
'emotion',
];

Expand Down
3 changes: 3 additions & 0 deletions build.tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@
{
"path": "./packages/radio-group"
},
{
"path": "./packages/select"
},
{
"path": "./packages/side-nav"
},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@babel/plugin-proposal-class-properties": "7.8.3",
"@babel/plugin-proposal-export-default-from": "7.8.3",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.8.3",
"@babel/plugin-proposal-logical-assignment-operators": "7.12.1",
"@babel/plugin-proposal-object-rest-spread": "7.9.5",
"@babel/plugin-proposal-optional-chaining": "7.9.0",
"@babel/plugin-transform-react-jsx": "^7.9.4",
Expand Down
133 changes: 133 additions & 0 deletions packages/select/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Select

![npm (scoped)](https://img.shields.io/npm/v/@leafygreen-ui/select.svg)

#### [View on Storybook](https://mongodb.github.io/leafygreen-ui/?path=/story/select--default)

## Installation

### Yarn

```shell
yarn add @leafygreen-ui/select
```

### NPM

```shell
npm install @leafygreen-ui/select
```

## Example

```js
import { Option, OptionGroup, Select, Size } from '@leafygreen-ui/select';

<Select
label="Label"
description="Description"
placeholder="Placeholder"
name="Name"
size={Size.Default}
defaultValue="cat"
>
<Option value="dog">Dog</Option>
<Option value="cat">Cat</Option>
<OptionGroup label="Less common">
<Option value="hamster">Hamster</Option>
<Option value="parrot">Parrot</Option>
</OptionGroup>
</Select>;
```

**Output HTML**

```html
<div class="">
<label id="select-7-label" class="leafygreen-ui-xzhurf">Label</label>
<div id="select-7-description" class="leafygreen-ui-3gds6m">Description</div>
<button
type="button"
class="leafygreen-ui-1fdrra0"
aria-disabled="false"
role="combobox"
aria-labelledby="select-7-label"
aria-controls="select-7-menu"
aria-expanded="false"
aria-describedby="select-7-description"
name="Name"
value="cat"
>
<span class="leafygreen-ui-tdo6z2"
><div class="leafygreen-ui-ogsjyj">
<span class="leafygreen-ui-1ks3bq2">Cat</span
><svg
class="leafygreen-ui-msi0rg"
height="16"
width="16"
viewBox="0 0 16 16"
role="img"
aria-labelledby="CaretDown-7"
>
<title id="CaretDown-7">Caret Down Icon</title>
<desc>Created with Sketch.</desc>
<g
id="CaretDown-Copy"
stroke="none"
stroke-width="1"
fill="none"
fill-rule="evenodd"
>
<path
d="M4.67285687,6 L11.3271431,6 C11.9254697,6 12.224633,6.775217 11.8024493,7.22717749 L8.47530616,10.7889853 C8.21248981,11.0703382 7.78751019,11.0703382 7.52748976,10.7889853 L4.19755071,7.22717749 C3.77536701,6.775217 4.07453029,6 4.67285687,6 Z"
id="Path"
fill="currentColor"
></path>
</g>
</svg>
</div>
<div></div
></span>
</button>
</div>
```

## Select Properties

| Prop | Type | Description | Default |
| ----------- | --------------------------------------------------- | ----------------------------------------------------------------- | ----------- |
| `children` | `node` | `<Option />` and `<OptionGroup />` elements. | |
| `className` | `string` | Adds a className to the outermost element. | |
| `darkMode` | `boolean` | Determines whether or not the component will appear in dark mode. | `false` |
| `size` | `'xsmall'` \| `'small'` \| `'default'` \| `'large'` | Sets the size of the component's elements. | `'default'` |
| `id` | `string` | id associated with the Select component. | |
| `name` | `string` | The name that will be used when submitted as part of a form. |

| `label` | `string` | Text shown in bold above the input element. | |
| `aria-labelledby` | `string` | Must be provided if and only if `label` is not provided. | |
| `description` | `string` | Text that gives more detail about the requirements for the input. | |
| `placeholder` | `string` | The placeholder text shown in the input element when an option is not selected. | `'Select'` |
| `disabled` | `boolean` | Disables the component from being edited. | `false` |
| `value` | `string` | Sets the `<Option />` that will appear selected and makes the component a controlled component. | `''` |
| `defaultValue` | `string` | Sets the `<Option />` that will appear selected on page load when the component is uncontrolled. | `''` |
| `onChange` | `function` | A function that gets called when the selected value changes. Receives the value string as the first argument. | `() => {}` |
| `readOnly` | `boolean` | Disables the console warning when the component is controlled and no `onChange` prop is provided. | `false` |

# Option

| Prop | Type | Description | Default |
| ----------- | -------------------- | ----------------------------------------------------------------------------------------------------- | --------------------------- |
| `children` | `node` | Content to appear inside of the component. | |
| `className` | `string` | Adds a className to the outermost element. | |
| `glyph` | `React.ReactElement` | Icon to display next to the option text. | |
| `value` | `string` | Corresponds to the value passed into the `onChange` prop of `<Select />` when the option is selected. | text contents of `children` |
| `disabled` | `boolean` | Prevents the option from being selectable. | `false` |

# OptionGroup

| Prop | Type | Description | Default |
| ----------- | --------- | --------------------------------------------------------- | ------- |
| `children` | `node` | `<Option />` elements | |
| `className` | `string` | Adds a className to the outermost element. | |
| `label` | `string` | Text shown above the group's options. | |
| `disabled` | `boolean` | Prevents all the contained options from being selectable. | `false` |
38 changes: 38 additions & 0 deletions packages/select/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@leafygreen-ui/select",
"version": "0.9.0",
"description": "leafyGreen UI Kit Select",
"main": "./dist/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/index.d.ts",
"typesVersions": {
"<3.9": {
"*": [
"ts3.4/*"
]
}
},
"scripts": {
"build": "../../node_modules/.bin/rollup --config ../../rollup.config.js"
},
"license": "Apache-2.0",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@leafygreen-ui/button": "^10.0.0",
"@leafygreen-ui/emotion": "^3.0.1",
"@leafygreen-ui/hooks": "^6.0.0",
"@leafygreen-ui/icon": "^7.0.2",
"@leafygreen-ui/lib": "^6.1.2",
"@leafygreen-ui/palette": "^3.1.0",
"@leafygreen-ui/popover": "^7.1.0",
"@leafygreen-ui/tokens": "0.5.0",
"@types/react-is": "^17.0.0",
"polished": "^4.0.3",
"react-is": "^17.0.1"
},
"peerDependencies": {
"@leafygreen-ui/leafygreen-provider": "^2.0.1"
}
}
154 changes: 154 additions & 0 deletions packages/select/src/ListMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import React, { useCallback, useContext } from 'react';
import { css, cx } from '@leafygreen-ui/emotion';
import { useViewportSize } from '@leafygreen-ui/hooks';
import { keyMap } from '@leafygreen-ui/lib';
import Popover, { Align, Justify } from '@leafygreen-ui/popover';
import { breakpoints } from '@leafygreen-ui/tokens';
import SelectContext from './SelectContext';
import { colorSets, mobileSizeSet, sizeSets } from './styleSets';
import { useForwardedRef } from './utils';

const menuStyle = css`
position: relative;
width: 100%;
border-radius: 3px;
line-height: 16px;
list-style: none;
margin: 0;
padding: 0;
overflow: auto;
`;

interface ListMenuProps {
children: React.ReactNode;
id: string;
referenceElement: React.MutableRefObject<HTMLElement | null>;
onClose: () => void;
onSelectFocusedOption: React.KeyboardEventHandler;
onFocusPreviousOption: () => void;
onFocusNextOption: () => void;
className?: string;
}

const ListMenu = React.forwardRef<HTMLUListElement, ListMenuProps>(
function ListMenu(
{
children,
id,
referenceElement,
onClose,
onFocusPreviousOption,
onFocusNextOption,
onSelectFocusedOption,
className,
}: ListMenuProps,
forwardedRef,
) {
const { mode, size, disabled, open } = useContext(SelectContext);

const colorSet = colorSets[mode];
const sizeSet = sizeSets[size];

const ref = useForwardedRef(forwardedRef, null);

const onKeyDown = useCallback(
(event: React.KeyboardEvent) => {
// No support for modifiers yet
/* istanbul ignore if */
if (event.ctrlKey || event.shiftKey || event.altKey) {
return;
}

let bubble = false;

switch (event.keyCode) {
case keyMap.Tab:
case keyMap.Enter:
onSelectFocusedOption(event);
break;
case keyMap.Escape:
onClose();
break;
case keyMap.ArrowUp:
onFocusPreviousOption();
break;
case keyMap.ArrowDown:
onFocusNextOption();
break;
/* istanbul ignore next */
default:
bubble = true;
}

/* istanbul ignore else */
if (!bubble) {
event.preventDefault();
event.stopPropagation();
}
},
[
onClose,
onFocusNextOption,
onFocusPreviousOption,
onSelectFocusedOption,
],
);

const viewportSize = useViewportSize();

const maxHeight =
viewportSize === null || ref.current === null
? 0
: viewportSize.height - ref.current.getBoundingClientRect().top - 10;

const onClick = useCallback(
(event: React.MouseEvent) => {
if (ref.current) {
ref.current.focus();
}
event.stopPropagation();
},
[ref],
);

return (
<Popover
active={open && !disabled}
spacing={4}
align={Align.Bottom}
justify={Justify.Middle}
adjustOnMutation
className={className}
refEl={referenceElement}
>
<ul
role="listbox"
ref={ref}
tabIndex={-1}
onKeyDown={onKeyDown}
onClick={onClick}
className={cx(
menuStyle,
css`
font-size: ${sizeSet.option.text}px;
max-height: ${maxHeight}px;
background-color: ${colorSet.option.background.base};
box-shadow: 0 3px 7px 0 ${colorSet.menu.shadow};

@media only screen and (max-width: ${breakpoints.Desktop}px) {
font-size: ${mobileSizeSet.option.text}px;
}
`,
)}
id={id}
>
{children}
</ul>
</Popover>
);
},
);

ListMenu.displayName = 'ListMenu';

export default ListMenu;
Loading