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

Use dropdown for session options when many choices #2461

Merged
merged 15 commits into from
Apr 17, 2023
Merged
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,12 @@ Here are the folders in `/client/src` where to place new components:

Picking the perfect place isn't always straightforward and our current folder structure still has many outdated components that don't follow the convention. We plan to move them when already touching the code for other changes.

### **Use of CSS modules**

[CSS modules](https://github.com/css-modules/css-modules) can be used to apply CSS styles locally and avoid leaking
styles to the whole web application.
[Create React App supports CSS modules out of the box](https://create-react-app.dev/docs/adding-a-css-modules-stylesheet).

# Server

The server is the [Express-based](https://expressjs.com) back-end for the RenkuLab UI. The main responsibilities of the server are managing user tokens acting handling requests for information from other backend services.
Expand Down
35 changes: 2 additions & 33 deletions client/src/notebooks/NotebookStart.present.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import React, { Component, Fragment, useEffect, useState } from "react";
import { Link, useLocation } from "react-router-dom";
import {
Badge, Button, ButtonGroup, Col, Collapse, DropdownItem, Form, FormGroup, FormText, Input, Label,
Badge, Button, Col, Collapse, DropdownItem, Form, FormGroup, FormText, Input, Label,
Modal, ModalBody, ModalFooter, ModalHeader, PopoverBody, PopoverHeader,
Row, UncontrolledPopover, UncontrolledTooltip
} from "reactstrap";
Expand Down Expand Up @@ -47,6 +47,7 @@ import { NotebooksHelper } from "./index";
import { ObjectStoresConfigurationButton, ObjectStoresConfigurationModal } from "./ObjectStoresConfig.present";
import EnvironmentVariables from "./components/EnviromentVariables";
import { useSelector } from "react-redux";
import { ServerOptionEnum } from "./components/StartNotebookServerOptions";
import { StartNotebookAutostartLoader, StartNotebookLoader } from "./components/StartSessionLoader";
import CommitSelector from "../components/commitSelector/CommitSelector";

Expand Down Expand Up @@ -1022,38 +1023,6 @@ class StartNotebookServerOptions extends Component {
}
}

class ServerOptionEnum extends Component {
render() {
const { disabled, selected } = this.props;
let { options } = this.props;

if (selected && options && options.length && !options.includes(selected))
options = options.concat(selected);
if (options.length === 1)
return (<Badge className="btn-outline-rk-green text-white">{this.props.options[0]}</Badge>);

return (
<ButtonGroup>
{options.map((optionName) => {
let color = "rk-white";
if (optionName === selected) {
color = this.props.warning != null && this.props.warning === optionName ?
"danger" :
undefined;
}
const size = this.props.size ? this.props.size : null;
return (
<Button
key={optionName} color={color} className="btn-outline-rk-green" size={size}
disabled={disabled} active={optionName === selected}
onClick={event => this.props.onChange(event, optionName)}>{optionName}</Button>
);
})}
</ButtonGroup>
);
}
}

class ServerOptionBoolean extends Component {
render() {
const { disabled } = this.props;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.dropdown {
:global .btn.dropdown-toggle {
border-radius: 8px;
border-width: 2.5px;
min-width: 160px;
padding: 5px 10px;
font-weight: bold;

display: inline-flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
align-items: center;

& span {
flex-grow: 1;
}

&::after {
margin-left: 6px;
}
}

:global .dropdown-menu {
padding: 0;
border: 0;
background: transparent;

:global .btn {
border: 1px solid var(--accent-color);
border-radius: unset;
background: white;
text-align: center;

&.active {
font-weight: bold;
border-width: 2.5px;
}
}
}
}
150 changes: 150 additions & 0 deletions client/src/notebooks/components/StartNotebookServerOptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*!
* Copyright 2023 - Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import React from "react";
import {
Badge,
Button,
ButtonGroup,
DropdownItem,
DropdownMenu,
DropdownToggle,
UncontrolledDropdown,
} from "reactstrap";

import styles from "./StartNotebookServerOptions.module.scss";

const FORM_MAX_WIDTH = 250; // pixels;

interface ServerOptionEnumProps<T extends string | number> {
disabled: boolean;
onChange: (
event: React.MouseEvent<HTMLElement, MouseEvent>,
optionName: T
) => void;
options: T[];
selected?: T | null | undefined;
size?: string | null | undefined;
warning?: T | null | undefined;
}

export const ServerOptionEnum = <T extends string | number>({
disabled,
onChange,
options,
selected,
size,
warning,
}: ServerOptionEnumProps<T>) => {
const safeOptions =
selected && options && options.length && !options.includes(selected)
? [...options, selected]
: options;

if (safeOptions.length === 1) {
return (
<Badge className="btn-outline-rk-green text-white">
{safeOptions[0]}
</Badge>
);
}

const approxSize = approximateButtonGroupSizeInPixels(safeOptions);
const useDropdown = approxSize > FORM_MAX_WIDTH;

if (useDropdown) {
const picked = selected ? selected : options[0];

let color: string | undefined = "rk-white";
if (picked === selected)
color = warning != null && warning === picked ? "danger" : undefined;

return (
<UncontrolledDropdown direction="down" className={styles.dropdown}>
<DropdownToggle
caret
className="btn-outline-rk-green"
size={size ?? undefined}
color={color}
>
<span>{picked}</span>
</DropdownToggle>
<DropdownMenu>
<ButtonGroup vertical className="w-100">
{safeOptions.map((optionName) => {
let color: string | undefined = "rk-white";
if (optionName === selected) {
color =
warning != null && warning === optionName
? "danger"
: undefined;
}
return (
<DropdownItem
key={optionName}
color={color}
className="btn-outline-rk-green btn"
size={size ?? undefined}
disabled={disabled}
active={optionName === selected}
onClick={(event) => onChange(event, optionName)}
style={{ border: "unset !important" }}
>
{optionName}
</DropdownItem>
);
})}
</ButtonGroup>
</DropdownMenu>
</UncontrolledDropdown>
);
}

return (
<ButtonGroup>
{safeOptions.map((optionName) => {
let color: string | undefined = "rk-white";
if (optionName === selected) {
color =
warning != null && warning === optionName ? "danger" : undefined;
}
return (
<Button
key={optionName}
color={color}
className="btn-outline-rk-green"
size={size ?? undefined}
disabled={disabled}
active={optionName === selected}
onClick={(event) => onChange(event, optionName)}
>
{optionName}
</Button>
);
})}
</ButtonGroup>
);
};

const approximateButtonGroupSizeInPixels = <T extends string | number>(
options: T[]
): number =>
// padding in x direction
options.length * 2 * 10 +
// safe approximate character size
options.map((opt) => `${opt}`).reduce((len, opt) => len + opt.length, 0) * 12;
10 changes: 10 additions & 0 deletions client/src/styles/bootstrap_ext/_button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,16 @@ $borderRadius: 1000px;
}
}

.btn-group-vertical:not(.btn-with-menu, .round-button-group) {
button:first-of-type {
border-radius: 8px 8px 0 0;
}

button:last-of-type {
border-radius: 0 0 8px 8px;
}
}

.btn-round {
border-radius: 1000px;
width: 50px;
Expand Down