mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-18 12:59:31 +00:00
Merge pull request #3337 from weaveworks/3314-use-new-search-bar
Use new Search component
This commit is contained in:
@@ -171,23 +171,29 @@ export function pinPreviousMetric() {
|
||||
};
|
||||
}
|
||||
|
||||
export function pinSearch() {
|
||||
export function updateSearch(searchQuery = '', pinnedSearches = []) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ActionTypes.PIN_SEARCH,
|
||||
query: getState().get('searchQuery'),
|
||||
type: ActionTypes.UPDATE_SEARCH,
|
||||
pinnedSearches,
|
||||
searchQuery,
|
||||
});
|
||||
updateRoute(getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function unpinSearch(query) {
|
||||
export function focusSearch() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ActionTypes.UNPIN_SEARCH,
|
||||
query
|
||||
});
|
||||
updateRoute(getState);
|
||||
dispatch({ type: ActionTypes.FOCUS_SEARCH });
|
||||
// update nodes cache to allow search across all topologies,
|
||||
// wait a second until animation is over
|
||||
// NOTE: This will cause matching recalculation (and rerendering)
|
||||
// of all the nodes in the topology, instead applying it only on
|
||||
// the nodes delta. The solution would be to implement deeper
|
||||
// search selectors with per-node caching instead of per-topology.
|
||||
setTimeout(() => {
|
||||
getAllNodes(getState(), dispatch);
|
||||
}, 1200);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -266,16 +272,6 @@ export function clickForceRelayout() {
|
||||
};
|
||||
}
|
||||
|
||||
export function doSearch(searchQuery) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ActionTypes.DO_SEARCH,
|
||||
searchQuery
|
||||
});
|
||||
updateRoute(getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function setViewportDimensions(width, height) {
|
||||
return (dispatch) => {
|
||||
dispatch({ type: ActionTypes.SET_VIEWPORT_DIMENSIONS, width, height });
|
||||
@@ -450,38 +446,6 @@ export function enterNode(nodeId) {
|
||||
};
|
||||
}
|
||||
|
||||
export function focusSearch() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: ActionTypes.FOCUS_SEARCH });
|
||||
// update nodes cache to allow search across all topologies,
|
||||
// wait a second until animation is over
|
||||
// NOTE: This will cause matching recalculation (and rerendering)
|
||||
// of all the nodes in the topology, instead applying it only on
|
||||
// the nodes delta. The solution would be to implement deeper
|
||||
// search selectors with per-node caching instead of per-topology.
|
||||
setTimeout(() => {
|
||||
getAllNodes(getState(), dispatch);
|
||||
}, 1200);
|
||||
};
|
||||
}
|
||||
|
||||
export function hitBackspace() {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
// remove last pinned query if search query is empty
|
||||
if (state.get('searchFocused') && !state.get('searchQuery')) {
|
||||
const query = state.get('pinnedSearches').last();
|
||||
if (query) {
|
||||
dispatch({
|
||||
type: ActionTypes.UNPIN_SEARCH,
|
||||
query
|
||||
});
|
||||
updateRoute(getState);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function hitEsc() {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
@@ -492,13 +456,6 @@ export function hitEsc() {
|
||||
pipeId: controlPipe.get('id')
|
||||
});
|
||||
updateRoute(getState);
|
||||
// Don't deselect node on ESC if there is a controlPipe (keep terminal open)
|
||||
} else if (state.get('searchFocused')) {
|
||||
if (state.get('searchQuery')) {
|
||||
dispatch(doSearch(''));
|
||||
} else {
|
||||
dispatch(blurSearch());
|
||||
}
|
||||
} else if (state.get('showingHelp')) {
|
||||
dispatch(hideHelp());
|
||||
} else if (state.get('nodeDetails').last() && !controlPipe) {
|
||||
@@ -634,9 +591,6 @@ export function receiveTopologies(topologies) {
|
||||
getNodes(getState, dispatch);
|
||||
// Populate search matches on first load
|
||||
const state = getState();
|
||||
if (firstLoad && state.get('searchQuery')) {
|
||||
dispatch(focusSearch());
|
||||
}
|
||||
// Fetch all the relevant nodes once on first load
|
||||
if (firstLoad && isResourceViewModeSelector(state)) {
|
||||
getResourceViewNodesSnapshot(state, dispatch);
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
focusSearch,
|
||||
pinNextMetric,
|
||||
pinPreviousMetric,
|
||||
hitBackspace,
|
||||
hitEsc,
|
||||
unpinMetric,
|
||||
toggleHelp,
|
||||
@@ -54,7 +53,6 @@ import {
|
||||
} from '../selectors/topology';
|
||||
import { VIEWPORT_RESIZE_DEBOUNCE_INTERVAL } from '../constants/timer';
|
||||
import {
|
||||
BACKSPACE_KEY_CODE,
|
||||
ESC_KEY_CODE,
|
||||
} from '../constants/key-codes';
|
||||
|
||||
@@ -116,8 +114,6 @@ class App extends React.Component {
|
||||
// don't get esc in onKeyPress
|
||||
if (ev.keyCode === ESC_KEY_CODE) {
|
||||
this.props.dispatch(hitEsc());
|
||||
} else if (ev.keyCode === BACKSPACE_KEY_CODE) {
|
||||
this.props.dispatch(hitBackspace());
|
||||
} else if (ev.code === 'KeyD' && ev.ctrlKey && !showingTerminal) {
|
||||
toggleDebugToolbar();
|
||||
this.forceUpdate();
|
||||
|
||||
@@ -12,7 +12,6 @@ const Match = match => (
|
||||
{match.label}:
|
||||
</span>
|
||||
<MatchedText
|
||||
noBorder
|
||||
text={match.text}
|
||||
match={match}
|
||||
maxLength={MAX_MATCH_LENGTH}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { unpinSearch } from '../actions/app-actions';
|
||||
|
||||
class SearchItem extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
handleClick(ev) {
|
||||
ev.preventDefault();
|
||||
this.props.unpinSearch(this.props.query);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span className="search-item">
|
||||
<span className="search-item-label">{this.props.query}</span>
|
||||
<span className="search-item-icon fa fa-close" onClick={this.handleClick} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(null, { unpinSearch })(SearchItem);
|
||||
@@ -1,19 +1,55 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import classnames from 'classnames';
|
||||
import { debounce } from 'lodash';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Search } from 'weaveworks-ui-components';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { blurSearch, doSearch, focusSearch, pinSearch, toggleHelp } from '../actions/app-actions';
|
||||
import { blurSearch, focusSearch, updateSearch, toggleHelp } from '../actions/app-actions';
|
||||
import { searchMatchCountByTopologySelector } from '../selectors/search';
|
||||
import { isResourceViewModeSelector } from '../selectors/topology';
|
||||
import { slugify } from '../utils/string-utils';
|
||||
import { parseQuery } from '../utils/search-utils';
|
||||
import { isTopologyNodeCountZero } from '../utils/topology-utils';
|
||||
import { trackAnalyticsEvent } from '../utils/tracking-utils';
|
||||
import SearchItem from './search-item';
|
||||
import { ENTER_KEY_CODE } from '../constants/key-codes';
|
||||
|
||||
|
||||
const SearchWrapper = styled.div`
|
||||
margin: 0 8px;
|
||||
min-width: 160px;
|
||||
text-align: right;
|
||||
`;
|
||||
|
||||
const SearchContainer = styled.div`
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
pointer-events: all;
|
||||
line-height: 100%;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const SearchHint = styled.div`
|
||||
font-size: ${props => props.theme.fontSizes.tiny};
|
||||
color: ${props => props.theme.colors.purple400};
|
||||
transition: transform 0.3s 0s ease-in-out, opacity 0.3s 0s ease-in-out;
|
||||
text-align: left;
|
||||
margin-top: 3px;
|
||||
padding: 0 1em;
|
||||
opacity: 0;
|
||||
|
||||
${props => props.active && `
|
||||
opacity: 1;
|
||||
`};
|
||||
`;
|
||||
|
||||
const SearchHintIcon = styled.span`
|
||||
font-size: ${props => props.theme.fontSizes.normal};
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: ${props => props.theme.colors.purple600};
|
||||
}
|
||||
`;
|
||||
|
||||
function shortenHintLabel(text) {
|
||||
return text
|
||||
.split(' ')[0]
|
||||
@@ -21,7 +57,6 @@ function shortenHintLabel(text) {
|
||||
.substr(0, 12);
|
||||
}
|
||||
|
||||
|
||||
// dynamic hint based on node names
|
||||
function getHint(nodes) {
|
||||
let label = 'mycontainer';
|
||||
@@ -39,145 +74,46 @@ function getHint(nodes) {
|
||||
}
|
||||
}
|
||||
|
||||
return `Try "${label}", "${metadataLabel}:${metadataValue}", or "cpu > 2%".
|
||||
Hit enter to apply the search as a filter.`;
|
||||
return `Try "${label}", "${metadataLabel}:${metadataValue}", or "cpu > 2%".`;
|
||||
}
|
||||
|
||||
|
||||
class Search extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.handleBlur = this.handleBlur.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleKeyUp = this.handleKeyUp.bind(this);
|
||||
this.handleFocus = this.handleFocus.bind(this);
|
||||
this.saveQueryInputRef = this.saveQueryInputRef.bind(this);
|
||||
this.doSearch = debounce(this.doSearch.bind(this), 200);
|
||||
this.state = {
|
||||
value: ''
|
||||
};
|
||||
}
|
||||
|
||||
handleBlur() {
|
||||
this.props.blurSearch();
|
||||
}
|
||||
|
||||
handleChange(ev) {
|
||||
const inputValue = ev.target.value;
|
||||
let value = inputValue;
|
||||
// In render() props.searchQuery can be set from the outside, but state.value
|
||||
// must have precedence for quick feedback. Now when the user backspaces
|
||||
// quickly enough from `text`, a previouse doSearch(`text`) will come back
|
||||
// via props and override the empty state.value. To detect this edge case
|
||||
// we instead set value to null when backspacing.
|
||||
if (this.state.value && value === '') {
|
||||
value = null;
|
||||
}
|
||||
this.setState({ value });
|
||||
this.doSearch(inputValue);
|
||||
}
|
||||
|
||||
handleKeyUp(ev) {
|
||||
// If the search query is parsable, pin it when ENTER key is hit.
|
||||
if (ev.keyCode === ENTER_KEY_CODE && parseQuery(this.props.searchQuery)) {
|
||||
trackAnalyticsEvent('scope.search.query.pin', {
|
||||
layout: this.props.topologyViewMode,
|
||||
topologyId: this.props.currentTopology.get('id'),
|
||||
parentTopologyId: this.props.currentTopology.get('parentId'),
|
||||
});
|
||||
this.props.pinSearch();
|
||||
}
|
||||
}
|
||||
|
||||
handleFocus() {
|
||||
this.props.focusSearch();
|
||||
}
|
||||
|
||||
doSearch(value) {
|
||||
if (value !== '') {
|
||||
trackAnalyticsEvent('scope.search.query.change', {
|
||||
layout: this.props.topologyViewMode,
|
||||
topologyId: this.props.currentTopology.get('id'),
|
||||
parentTopologyId: this.props.currentTopology.get('parentId'),
|
||||
});
|
||||
}
|
||||
this.props.doSearch(value);
|
||||
}
|
||||
|
||||
saveQueryInputRef(ref) {
|
||||
this.queryInput = ref;
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// when cleared from the outside, reset internal state
|
||||
if (this.props.searchQuery !== nextProps.searchQuery && nextProps.searchQuery === '') {
|
||||
this.setState({ value: '' });
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.props.searchFocused) {
|
||||
this.queryInput.focus();
|
||||
} else if (!this.state.value) {
|
||||
this.queryInput.blur();
|
||||
}
|
||||
class SearchComponent extends React.Component {
|
||||
handleChange = (searchQuery, pinnedSearches) => {
|
||||
trackAnalyticsEvent('scope.search.query.change', {
|
||||
layout: this.props.topologyViewMode,
|
||||
topologyId: this.props.currentTopology.get('id'),
|
||||
parentTopologyId: this.props.currentTopology.get('parentId'),
|
||||
});
|
||||
this.props.updateSearch(searchQuery, pinnedSearches);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
nodes, pinnedSearches, searchFocused, searchMatchCountByTopology,
|
||||
isResourceViewMode, searchQuery, topologiesLoaded, inputId = 'search'
|
||||
searchHint, searchMatchesCount, searchQuery, pinnedSearches, topologiesLoaded,
|
||||
isResourceViewMode, isTopologyEmpty,
|
||||
} = this.props;
|
||||
const hidden = !topologiesLoaded || isResourceViewMode;
|
||||
const disabled = this.props.isTopologyNodeCountZero && !hidden;
|
||||
const matchCount = searchMatchCountByTopology
|
||||
.reduce((count, topologyMatchCount) => count + topologyMatchCount, 0);
|
||||
const showPinnedSearches = pinnedSearches.size > 0;
|
||||
// manual clear (null) has priority, then props, then state
|
||||
const value = this.state.value === null ? '' : this.state.value || searchQuery || '';
|
||||
const classNames = classnames('search', 'hideable', {
|
||||
hide: hidden,
|
||||
'search-pinned': showPinnedSearches,
|
||||
'search-matched': matchCount,
|
||||
'search-filled': value,
|
||||
'search-focused': searchFocused,
|
||||
'search-disabled': disabled
|
||||
});
|
||||
const title = matchCount ? `${matchCount} matches` : null;
|
||||
|
||||
return (
|
||||
<div className="search-wrapper">
|
||||
<div className={classNames} title={title}>
|
||||
<div className="search-input">
|
||||
{showPinnedSearches && pinnedSearches.toIndexedSeq()
|
||||
.map(query => <SearchItem query={query} key={query} />)}
|
||||
<input
|
||||
className="search-input-field"
|
||||
type="text"
|
||||
id={inputId}
|
||||
value={value}
|
||||
onChange={this.handleChange}
|
||||
onKeyUp={this.handleKeyUp}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
disabled={disabled}
|
||||
ref={this.saveQueryInputRef} />
|
||||
</div>
|
||||
<div className="search-label">
|
||||
<i className="fa fa-search search-label-icon" />
|
||||
<span className="search-label-hint" htmlFor={inputId}>
|
||||
Search
|
||||
</span>
|
||||
</div>
|
||||
{!showPinnedSearches &&
|
||||
<div className="search-hint">
|
||||
{getHint(nodes)} <span
|
||||
className="search-help-link fa fa-question-circle"
|
||||
onMouseDown={this.props.toggleHelp} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<SearchWrapper>
|
||||
<SearchContainer title={searchMatchesCount ? `${searchMatchesCount} matches` : undefined}>
|
||||
<Search
|
||||
placeholder="search"
|
||||
query={searchQuery}
|
||||
pinnedTerms={pinnedSearches}
|
||||
disabled={topologiesLoaded && !isResourceViewMode && isTopologyEmpty}
|
||||
onChange={this.handleChange}
|
||||
onFocus={this.props.focusSearch}
|
||||
onBlur={this.props.blurSearch}
|
||||
/>
|
||||
<SearchHint active={this.props.searchFocused && isEmpty(pinnedSearches)}>
|
||||
{searchHint} <SearchHintIcon
|
||||
className="fa fa-question-circle"
|
||||
onMouseDown={this.props.toggleHelp}
|
||||
/>
|
||||
</SearchHint>
|
||||
</SearchContainer>
|
||||
</SearchWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -185,18 +121,19 @@ class Search extends React.Component {
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
nodes: state.get('nodes'),
|
||||
searchHint: getHint(state.get('nodes')),
|
||||
searchFocused: state.get('searchFocused'),
|
||||
topologyViewMode: state.get('topologyViewMode'),
|
||||
isResourceViewMode: isResourceViewModeSelector(state),
|
||||
isTopologyNodeCountZero: isTopologyNodeCountZero(state),
|
||||
isTopologyEmpty: isTopologyNodeCountZero(state),
|
||||
currentTopology: state.get('currentTopology'),
|
||||
topologiesLoaded: state.get('topologiesLoaded'),
|
||||
pinnedSearches: state.get('pinnedSearches'),
|
||||
searchFocused: state.get('searchFocused'),
|
||||
pinnedSearches: state.get('pinnedSearches').toJS(),
|
||||
searchQuery: state.get('searchQuery'),
|
||||
searchMatchCountByTopology: searchMatchCountByTopologySelector(state),
|
||||
searchMatchesCount: searchMatchCountByTopologySelector(state)
|
||||
.reduce((count, topologyMatchCount) => count + topologyMatchCount, 0),
|
||||
}),
|
||||
{
|
||||
blurSearch, doSearch, focusSearch, pinSearch, toggleHelp
|
||||
blurSearch, focusSearch, updateSearch, toggleHelp
|
||||
}
|
||||
)(Search);
|
||||
)(SearchComponent);
|
||||
|
||||
@@ -22,7 +22,6 @@ const ACTION_TYPES = [
|
||||
'DO_CONTROL_ERROR',
|
||||
'DO_CONTROL_SUCCESS',
|
||||
'DO_CONTROL',
|
||||
'DO_SEARCH',
|
||||
'ENTER_EDGE',
|
||||
'ENTER_NODE',
|
||||
'FINISH_TIME_TRAVEL_TRANSITION',
|
||||
@@ -37,7 +36,6 @@ const ACTION_TYPES = [
|
||||
'PAUSE_TIME_AT_NOW',
|
||||
'PIN_METRIC',
|
||||
'PIN_NETWORK',
|
||||
'PIN_SEARCH',
|
||||
'RECEIVE_API_DETAILS',
|
||||
'RECEIVE_CONTROL_NODE_REMOVED',
|
||||
'RECEIVE_CONTROL_PIPE_STATUS',
|
||||
@@ -67,7 +65,7 @@ const ACTION_TYPES = [
|
||||
'UNHOVER_METRIC',
|
||||
'UNPIN_METRIC',
|
||||
'UNPIN_NETWORK',
|
||||
'UNPIN_SEARCH',
|
||||
'UPDATE_SEARCH',
|
||||
];
|
||||
|
||||
export default zipObject(ACTION_TYPES, ACTION_TYPES);
|
||||
|
||||
@@ -70,7 +70,6 @@ export const initialState = makeMap({
|
||||
plugins: makeList(),
|
||||
pinnedSearches: makeList(), // list of node filters
|
||||
routeSet: false,
|
||||
storeViewState: true,
|
||||
searchFocused: false,
|
||||
searchQuery: '',
|
||||
selectedNetwork: null,
|
||||
@@ -78,6 +77,7 @@ export const initialState = makeMap({
|
||||
showingHelp: false,
|
||||
showingTroubleshootingMenu: false,
|
||||
showingNetworks: false,
|
||||
storeViewState: true,
|
||||
timeTravelTransitioning: false,
|
||||
topologies: makeList(),
|
||||
topologiesLoaded: false,
|
||||
@@ -219,6 +219,10 @@ export function rootReducer(state = initialState, action) {
|
||||
return state.set('searchFocused', false);
|
||||
}
|
||||
|
||||
case ActionTypes.FOCUS_SEARCH: {
|
||||
return state.set('searchFocused', true);
|
||||
}
|
||||
|
||||
case ActionTypes.CHANGE_TOPOLOGY_OPTION: {
|
||||
// set option on parent topology
|
||||
const topology = findTopologyById(state.get('topologies'), action.topologyId);
|
||||
@@ -466,10 +470,6 @@ export function rootReducer(state = initialState, action) {
|
||||
}));
|
||||
}
|
||||
|
||||
case ActionTypes.DO_SEARCH: {
|
||||
return state.set('searchQuery', action.searchQuery);
|
||||
}
|
||||
|
||||
case ActionTypes.ENTER_EDGE: {
|
||||
return state.set('mouseOverEdgeId', action.edgeId);
|
||||
}
|
||||
@@ -499,14 +499,9 @@ export function rootReducer(state = initialState, action) {
|
||||
}));
|
||||
}
|
||||
|
||||
case ActionTypes.FOCUS_SEARCH: {
|
||||
return state.set('searchFocused', true);
|
||||
}
|
||||
|
||||
case ActionTypes.PIN_SEARCH: {
|
||||
const pinnedSearches = state.get('pinnedSearches');
|
||||
state = state.setIn(['pinnedSearches', pinnedSearches.size], action.query);
|
||||
state = state.set('searchQuery', '');
|
||||
case ActionTypes.UPDATE_SEARCH: {
|
||||
state = state.set('pinnedSearches', makeList(action.pinnedSearches));
|
||||
state = state.set('searchQuery', action.searchQuery || '');
|
||||
return applyPinnedSearches(state);
|
||||
}
|
||||
|
||||
@@ -722,12 +717,6 @@ export function rootReducer(state = initialState, action) {
|
||||
return state;
|
||||
}
|
||||
|
||||
case ActionTypes.UNPIN_SEARCH: {
|
||||
const pinnedSearches = state.get('pinnedSearches').filter(query => query !== action.query);
|
||||
state = state.set('pinnedSearches', pinnedSearches);
|
||||
return applyPinnedSearches(state);
|
||||
}
|
||||
|
||||
case ActionTypes.DEBUG_TOOLBAR_INTERFERING: {
|
||||
return action.fn(state);
|
||||
}
|
||||
|
||||
@@ -1410,153 +1410,11 @@ a {
|
||||
}
|
||||
|
||||
.search {
|
||||
pointer-events: all;
|
||||
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 10em;
|
||||
transition: width 0.3s 0s $base-ease;
|
||||
|
||||
&-wrapper {
|
||||
margin: 0 8px;
|
||||
min-width: 160px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&-disabled {
|
||||
opacity: 0.5;
|
||||
cursor: disabled;
|
||||
}
|
||||
|
||||
&-hint {
|
||||
font-size: $font-size-tiny;
|
||||
position: absolute;
|
||||
padding: 0 1em;
|
||||
color: $text-tertiary-color;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
transition: transform 0.3s 0s $base-ease, opacity 0.3s 0s $base-ease;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&-help-link {
|
||||
@extend .btn-opacity;
|
||||
cursor: pointer;
|
||||
font-size: $font-size-normal;
|
||||
}
|
||||
|
||||
&-label {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
top: 0;
|
||||
left: 4px;
|
||||
z-index: $layer-front;
|
||||
padding: 4px;
|
||||
color: $text-secondary-color;
|
||||
|
||||
&-icon {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
&-hint {
|
||||
font-size: $font-size-small;
|
||||
transition: opacity 0.3s 0.5s $base-ease;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&-input {
|
||||
overflow: hidden;
|
||||
background: $color-white;
|
||||
position: relative;
|
||||
z-index: $layer-front;
|
||||
display: flex;
|
||||
border-radius: $border-radius-soft;
|
||||
width: 100%;
|
||||
border: $search-border-width solid $search-border-color;
|
||||
padding: 2px 4px;
|
||||
text-align: left;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&-field {
|
||||
font-size: $font-size-small;
|
||||
line-height: 150%;
|
||||
position: relative;
|
||||
padding: 1px 4px 1px 1.5em;
|
||||
border: none;
|
||||
border-radius: $border-radius-none;
|
||||
background: transparent;
|
||||
color: $text-color;
|
||||
width: 100px;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-focused &-label-hint,
|
||||
&-pinned &-label-hint,
|
||||
&-filled &-label-hint {
|
||||
transition: opacity 0.1s 0s $base-ease;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&-focused &-hint,
|
||||
&-filled &-hint,
|
||||
&-pinned &-hint {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 2.75em, 0);
|
||||
transition: transform 0.3s 0.3s $base-ease, opacity 0.3s 0.3s $base-ease;
|
||||
}
|
||||
|
||||
&-focused &-input-field,
|
||||
&-filled &-input-field,
|
||||
&-pinned &-input-field {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&-focused,
|
||||
&-filled,
|
||||
&-pinned {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-matched &-input {
|
||||
border-color: $color-blue-400;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.search-item {
|
||||
background-color: transparentize($color-blue-400, 0.2);
|
||||
border-radius: $border-radius-soft;
|
||||
margin: 1px 0 1px 1.5em;
|
||||
padding: 2px 4px 2px 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& + .search-item {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
& + .search-input-field {
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
&-label {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
@extend .btn-opacity;
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
font-size: $font-size-small;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blinking {
|
||||
@@ -1683,6 +1541,9 @@ a {
|
||||
&-term {
|
||||
flex: 1;
|
||||
color: $text-secondary-color;
|
||||
i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&-term-label {
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"reselect": "3.0.1",
|
||||
"reselect-map": "1.0.3",
|
||||
"styled-components": "2.2.4",
|
||||
"weaveworks-ui-components": "0.11.14",
|
||||
"weaveworks-ui-components": "0.11.31",
|
||||
"whatwg-fetch": "2.0.3",
|
||||
"xterm": "3.3.0"
|
||||
},
|
||||
|
||||
@@ -8401,9 +8401,9 @@ wd@^0.4.0:
|
||||
underscore.string "~3.0.3"
|
||||
vargs "~0.1.0"
|
||||
|
||||
weaveworks-ui-components@0.11.14:
|
||||
version "0.11.14"
|
||||
resolved "https://registry.yarnpkg.com/weaveworks-ui-components/-/weaveworks-ui-components-0.11.14.tgz#5f72f8a5059bc9f28295b91164c17e0780906f9e"
|
||||
weaveworks-ui-components@0.11.31:
|
||||
version "0.11.31"
|
||||
resolved "https://registry.yarnpkg.com/weaveworks-ui-components/-/weaveworks-ui-components-0.11.31.tgz#90392fe5aafd6a911d985ef9ef554cb7178474d5"
|
||||
dependencies:
|
||||
classnames "2.2.5"
|
||||
d3-drag "1.2.1"
|
||||
@@ -8416,6 +8416,7 @@ weaveworks-ui-components@0.11.14:
|
||||
react-input-autosize "2.2.1"
|
||||
react-motion "0.5.2"
|
||||
react-resize-aware "2.7.0"
|
||||
react-router "3.2.0"
|
||||
|
||||
webidl-conversions@^3.0.0:
|
||||
version "3.0.1"
|
||||
|
||||
Reference in New Issue
Block a user