Polish Inline Label Creation (#12070)
* Condense appearance of inline create label form Co-Authored-By: Delmer <ofthedelmer@users.noreply.github.com> * Fine tune copy and click outside behavior Co-Authored-By: Delmer <ofthedelmer@users.noreply.github.com> * Show label color error color when invalid hex Co-Authored-By: Delmer <ofthedelmer@users.noreply.github.com> * Shrink width of random label color button Co-Authored-By: Delmer <ofthedelmer@users.noreply.github.com> * Add full color palette to list of random label colors Co-Authored-By: Delmer <ofthedelmer@users.noreply.github.com> * fix(ui/prettier-errors): fix ui prettier errors Co-authored-by: Alex Paxton <thealexpaxton@gmail.com>pull/12088/head
parent
c664e8e0d8
commit
80ebf933aa
|
@ -26,30 +26,34 @@
|
|||
}
|
||||
|
||||
.label-selector--menu {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
min-height: 50px;
|
||||
padding: $ix-marg-b;
|
||||
padding: $ix-marg-b - ($ix-border / 2);
|
||||
}
|
||||
|
||||
.label-selector--menu-item {
|
||||
margin: 1px;
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
margin: $ix-border / 2;
|
||||
}
|
||||
|
||||
.label-selector--empty {
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.label-selector--empty {
|
||||
padding: $ix-marg-a $ix-marg-b;
|
||||
color: $g9-mountain;
|
||||
font-style: italic;
|
||||
min-height: 30px;
|
||||
line-height: 30px;
|
||||
|
||||
&:first-child {
|
||||
margin-bottom: $ix-marg-b - ($ix-border / 2);
|
||||
}
|
||||
}
|
||||
|
||||
.label-selector--selection {
|
||||
|
|
|
@ -74,15 +74,15 @@ class LabelSelector extends Component<Props, State> {
|
|||
|
||||
public render() {
|
||||
return (
|
||||
<ClickOutside onClickOutside={this.handleStopSuggesting}>
|
||||
<div className="label-selector">
|
||||
<div className="label-selector--selection">
|
||||
{this.selectedLabels}
|
||||
{this.clearSelectedButton}
|
||||
</div>
|
||||
{this.input}
|
||||
<div className="label-selector">
|
||||
<div className="label-selector--selection">
|
||||
{this.selectedLabels}
|
||||
{this.clearSelectedButton}
|
||||
</div>
|
||||
</ClickOutside>
|
||||
<ClickOutside onClickOutside={this.handleStopSuggesting}>
|
||||
{this.input}
|
||||
</ClickOutside>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -169,8 +169,9 @@ class LabelSelector extends Component<Props, State> {
|
|||
|
||||
private handleStartSuggesting = () => {
|
||||
const {availableLabels} = this
|
||||
const {isSuggesting} = this.state
|
||||
|
||||
if (_.isEmpty(availableLabels)) {
|
||||
if (_.isEmpty(availableLabels) && !isSuggesting) {
|
||||
return this.setState({
|
||||
isSuggesting: true,
|
||||
highlightedID: null,
|
||||
|
|
|
@ -30,8 +30,9 @@ class LabelSelectorMenu extends Component<Props> {
|
|||
<div className="label-selector--menu-container">
|
||||
<FancyScrollbar autoHide={false} autoHeight={true} maxHeight={250}>
|
||||
<div className="label-selector--menu">
|
||||
{this.resourceLabelForm}
|
||||
{this.menuItems}
|
||||
{this.emptyText}
|
||||
{this.resourceLabelForm}
|
||||
</div>
|
||||
</FancyScrollbar>
|
||||
</div>
|
||||
|
@ -60,18 +61,22 @@ class LabelSelectorMenu extends Component<Props> {
|
|||
/>
|
||||
))
|
||||
}
|
||||
|
||||
return <div className="label-selector--empty">{this.emptyText}</div>
|
||||
}
|
||||
|
||||
private get emptyText(): string {
|
||||
const {allLabelsUsed} = this.props
|
||||
private get emptyText(): JSX.Element {
|
||||
const {allLabelsUsed, filterValue} = this.props
|
||||
|
||||
if (allLabelsUsed) {
|
||||
return 'You have somehow managed to add all the labels, wow!'
|
||||
if (!filterValue) {
|
||||
return null
|
||||
}
|
||||
|
||||
return 'No labels match your query'
|
||||
let text = `No labels match "${filterValue}" want to create a new label?`
|
||||
|
||||
if (allLabelsUsed) {
|
||||
text = 'You have somehow managed to add all the labels, wow!'
|
||||
}
|
||||
|
||||
return <div className="label-selector--empty">{text}</div>
|
||||
}
|
||||
|
||||
private get resourceLabelForm(): JSX.Element {
|
||||
|
|
|
@ -26,9 +26,9 @@ class LabelSelectorMenuItem extends Component<Props> {
|
|||
<span
|
||||
className="label-selector--menu-item"
|
||||
onMouseOver={this.handleMouseOver}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<Label
|
||||
onClick={this.handleClick}
|
||||
name={name}
|
||||
description={description}
|
||||
id={id}
|
||||
|
|
|
@ -9,9 +9,12 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: $ix-marg-b;
|
||||
|
||||
.label-colors--swatch {
|
||||
margin-right: $ix-marg-c;
|
||||
margin-right: $ix-marg-b;
|
||||
}
|
||||
|
||||
> span.button-icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
|
@ -4,10 +4,14 @@ import _ from 'lodash'
|
|||
// Utils
|
||||
import {randomPresetColor} from 'src/configuration/utils/labels'
|
||||
import {IconFont} from 'src/clockface'
|
||||
import {validateHexCode} from 'src/configuration/utils/labels'
|
||||
|
||||
// Styles
|
||||
import 'src/configuration/components/RandomLabelColor.scss'
|
||||
|
||||
// Constants
|
||||
import {INPUT_ERROR_COLOR} from 'src/configuration/constants/LabelColors'
|
||||
|
||||
interface Props {
|
||||
colorHex: string
|
||||
onClick: (newRandomHex: string) => void
|
||||
|
@ -15,16 +19,16 @@ interface Props {
|
|||
|
||||
export default class RandomLabelColorButton extends Component<Props> {
|
||||
public render() {
|
||||
const {colorHex} = this.props
|
||||
return (
|
||||
<button
|
||||
className="button button-sm button-default random-color--button "
|
||||
onClick={this.handleClick}
|
||||
title="Randomize label color"
|
||||
>
|
||||
<div
|
||||
className="label-colors--swatch"
|
||||
style={{
|
||||
backgroundColor: colorHex,
|
||||
backgroundColor: this.colorHex,
|
||||
}}
|
||||
/>
|
||||
<span className={`button-icon icon ${IconFont.Refresh}`} />
|
||||
|
@ -32,6 +36,16 @@ export default class RandomLabelColorButton extends Component<Props> {
|
|||
)
|
||||
}
|
||||
|
||||
private get colorHex(): string {
|
||||
const {colorHex} = this.props
|
||||
|
||||
if (validateHexCode(colorHex)) {
|
||||
return INPUT_ERROR_COLOR
|
||||
}
|
||||
|
||||
return colorHex
|
||||
}
|
||||
|
||||
private handleClick = () => {
|
||||
this.props.onClick(randomPresetColor())
|
||||
}
|
||||
|
|
|
@ -58,6 +58,150 @@ export const PRESET_LABEL_COLORS: LabelColor[] = [
|
|||
name: 'Neutrino',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-void',
|
||||
colorHex: '#311F94',
|
||||
name: 'Void',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-amethyst',
|
||||
colorHex: '#513CC6',
|
||||
name: 'Amethyst',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-star',
|
||||
colorHex: '#7A65F2',
|
||||
name: 'Star',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-comet',
|
||||
colorHex: '#9394FF',
|
||||
name: 'Comet',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-potassium',
|
||||
colorHex: '#B1B6FF',
|
||||
name: 'Potassium',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-moonstone',
|
||||
colorHex: '#C9D0FF',
|
||||
name: 'Moonstone',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-emerald',
|
||||
colorHex: '#108174',
|
||||
name: 'Emerald',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-viridian',
|
||||
colorHex: '#32B08C',
|
||||
name: 'Viridian',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-rainforest',
|
||||
colorHex: '#4ED8A0',
|
||||
name: 'Rainforest',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-honeydew',
|
||||
colorHex: '#7CE490',
|
||||
name: 'Honeydew',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-krypton',
|
||||
colorHex: '#A5F3B4',
|
||||
name: 'Krypton',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-wasabi',
|
||||
colorHex: '#C6FFD0',
|
||||
name: 'Wasabi',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-ruby',
|
||||
colorHex: '#BF3D5E',
|
||||
name: 'Ruby',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-fire',
|
||||
colorHex: '#DC4E58',
|
||||
name: 'Fire',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-curacao',
|
||||
colorHex: '#F95F53',
|
||||
name: 'Curacao',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-dreamsicle',
|
||||
colorHex: '#FF8564',
|
||||
name: 'Dreamsicle',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-tungsten',
|
||||
colorHex: '#FFB6A0',
|
||||
name: 'Tungsten',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-marmelade',
|
||||
colorHex: '#FFDCCF',
|
||||
name: 'Marmelade',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-topaz',
|
||||
colorHex: '#E85B1C',
|
||||
name: 'Topaz',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-tiger',
|
||||
colorHex: '#F48D38',
|
||||
name: 'Tiger',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-pineapple',
|
||||
colorHex: '#FFB94A',
|
||||
name: 'Pineapple',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-thunder',
|
||||
colorHex: '#FFD255',
|
||||
name: 'Thunder',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-sulfur',
|
||||
colorHex: '#FFE480',
|
||||
name: 'Sulfur',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
{
|
||||
id: 'label-preset-daisy',
|
||||
colorHex: '#FFF6B8',
|
||||
name: 'Daisy',
|
||||
type: LabelColorType.Preset,
|
||||
},
|
||||
]
|
||||
|
||||
export const INPUT_ERROR_COLOR = '#0F0E15'
|
||||
|
|
|
@ -5,9 +5,12 @@
|
|||
*/
|
||||
|
||||
.resource-label--form {
|
||||
margin-bottom: $ix-marg-b;
|
||||
width: 100%;
|
||||
|
||||
.resource-label--create-button {
|
||||
margin-top: $ix-marg-c + 2px;
|
||||
}
|
||||
}
|
||||
.form--element {
|
||||
margin: 0;
|
||||
}
|
||||
.component-spacer--horizontal {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,22 +7,15 @@ import {
|
|||
Button,
|
||||
ComponentColor,
|
||||
ButtonType,
|
||||
Columns,
|
||||
ComponentStatus,
|
||||
} from '@influxdata/clockface'
|
||||
import {
|
||||
Grid,
|
||||
Form,
|
||||
Input,
|
||||
InputType,
|
||||
ComponentSpacer,
|
||||
Alignment,
|
||||
} from 'src/clockface'
|
||||
import {Form, Input, InputType, ComponentSpacer, Alignment} from 'src/clockface'
|
||||
import RandomLabelColorButton from 'src/configuration/components/RandomLabelColor'
|
||||
import {Label, LabelProperties} from 'src/types/v2/labels'
|
||||
|
||||
// Constants
|
||||
import {HEX_CODE_CHAR_LENGTH} from 'src/configuration/constants/LabelColors'
|
||||
const MAX_CREATE_BUTTON_LENGTH = 24
|
||||
|
||||
// Utils
|
||||
import {
|
||||
|
@ -72,46 +65,34 @@ export default class ResourceLabelForm extends PureComponent<Props, State> {
|
|||
public render() {
|
||||
const {isValid} = this.state
|
||||
|
||||
// TODO: Add className prop to ComponentSpacer so we don't need this wrapper div
|
||||
|
||||
return (
|
||||
<div className="resource-label--form">
|
||||
<Grid.Row>
|
||||
<Grid.Column widthXS={Columns.Five}>
|
||||
<Form.Element label="Color">
|
||||
<ComponentSpacer stretchToFitWidth={true} align={Alignment.Left}>
|
||||
<RandomLabelColorButton
|
||||
colorHex={this.colorHex}
|
||||
onClick={this.handleColorChange}
|
||||
/>
|
||||
{this.customColorInput}
|
||||
</ComponentSpacer>
|
||||
</Form.Element>
|
||||
</Grid.Column>
|
||||
<Grid.Column widthXS={Columns.Five}>
|
||||
<Form.Element label="Description">
|
||||
<Input
|
||||
type={InputType.Text}
|
||||
placeholder="Add a optional description"
|
||||
name="description"
|
||||
value={this.description}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
</Form.Element>
|
||||
</Grid.Column>
|
||||
<Grid.Column widthXS={Columns.Two}>
|
||||
<Form.Element label="">
|
||||
<Button
|
||||
customClass="resource-label--create-button"
|
||||
text="Create Label"
|
||||
color={ComponentColor.Success}
|
||||
type={ButtonType.Submit}
|
||||
status={
|
||||
isValid ? ComponentStatus.Default : ComponentStatus.Disabled
|
||||
}
|
||||
onClick={this.handleSubmit}
|
||||
/>
|
||||
</Form.Element>
|
||||
</Grid.Column>
|
||||
</Grid.Row>
|
||||
<ComponentSpacer align={Alignment.Left}>
|
||||
<RandomLabelColorButton
|
||||
colorHex={this.colorHex}
|
||||
onClick={this.handleColorChange}
|
||||
/>
|
||||
{this.customColorInput}
|
||||
<Input
|
||||
type={InputType.Text}
|
||||
placeholder="Add a optional description"
|
||||
name="description"
|
||||
value={this.description}
|
||||
onChange={this.handleInputChange}
|
||||
/>
|
||||
<Button
|
||||
customClass="resource-label--create-button"
|
||||
text={this.createButtonLabel}
|
||||
color={ComponentColor.Success}
|
||||
type={ButtonType.Submit}
|
||||
status={
|
||||
isValid ? ComponentStatus.Default : ComponentStatus.Disabled
|
||||
}
|
||||
onClick={this.handleSubmit}
|
||||
/>
|
||||
</ComponentSpacer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -157,6 +138,18 @@ export default class ResourceLabelForm extends PureComponent<Props, State> {
|
|||
})
|
||||
}
|
||||
|
||||
private get createButtonLabel(): string {
|
||||
const {labelName} = this.props
|
||||
|
||||
let label = `Create "${labelName}"`
|
||||
|
||||
if (labelName.length > MAX_CREATE_BUTTON_LENGTH) {
|
||||
label = `Create "${labelName.slice(0, MAX_CREATE_BUTTON_LENGTH)}..."`
|
||||
}
|
||||
|
||||
return label
|
||||
}
|
||||
|
||||
private get customColorInput(): JSX.Element {
|
||||
const {colorHex} = this
|
||||
|
||||
|
|
Loading…
Reference in New Issue