Skip to content

Commit 3a94014

Browse files
multi drop down support
1 parent 5ed81c0 commit 3a94014

12 files changed

Lines changed: 163 additions & 69 deletions

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"dependencies": {
2525
"react": "^16.4.0",
2626
"react-dom": "^16.4.0",
27-
"url-search-params-polyfill": "^3.0.0"
27+
"url-search-params-polyfill": "^5.1.0"
2828
},
2929
"scripts": {
3030
"start": "react-scripts-ts start",

src/App.tsx

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,40 @@ export class App extends React.Component {
2828
return (
2929
<ComponentViewer
3030
registries={registries}
31-
dropDown={{
32-
label: 'Brand',
33-
items: [
34-
{label: 'Brand-A', hotKey: 'Alt 1'},
35-
{label: 'B-Brand', hotKey: 'Alt 2'}
36-
],
37-
onSelect: this.onBrandSelect
38-
}}
31+
dropDowns={[this.createServiceDropDown(), this.createBrandDropDown()]}
3932
/>
4033
);
4134
}
4235

43-
private onBrandSelect(brand: string) {
36+
private createBrandDropDown() {
37+
return {
38+
label: 'Brand',
39+
items: [
40+
{label: 'Brand-A', hotKey: 'Alt 1'},
41+
{label: 'B-Brand', hotKey: 'Alt 2'}
42+
],
43+
onSelect: this.onBrandSelect
44+
};
45+
}
46+
47+
private createServiceDropDown() {
48+
return {
49+
label: 'Services',
50+
items: [
51+
{label: 'Fake', hotKey: 'Alt 5'},
52+
{label: 'REST', hotKey: 'Alt 6'}
53+
],
54+
onSelect: this.onServiceSelect
55+
};
56+
}
57+
58+
private onBrandSelect = (brand: string) => {
4459
console.log('selected brand', brand);
4560
}
61+
62+
private onServiceSelect = (service: string) => {
63+
console.log('selected service', service);
64+
}
4665
}
4766

4867
function DemoWrapper({OriginalComponent}: WrapperProps) {

src/components/viewer/ComponentViewer.tsx

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ import { VisualizedActions } from '../actions/VisualizedActions';
2828

2929
import './ComponentViewer.css';
3030

31+
import { labelToKey } from './toolbar/labelUtils';
32+
3133
export interface Props {
3234
registries: Registries;
33-
dropDown?: ComponentViewerDropDown;
35+
dropDowns: ComponentViewerDropDown[];
3436
}
3537

3638
class ComponentViewer extends Component<Props, ComponentViewerState> {
@@ -79,13 +81,13 @@ class ComponentViewer extends Component<Props, ComponentViewerState> {
7981
}
8082

8183
renderSelectionPanelAndDemo(demoEntry: DemoEntry | null) {
82-
const {registries, dropDown} = this.props;
84+
const {registries, dropDowns} = this.props;
8385

8486
const {
8587
registryName,
8688
demoName,
8789
filterText,
88-
selectedToolbarItem,
90+
selectedToolbarItems,
8991
isHelpOn
9092
} = this.state;
9193

@@ -102,10 +104,9 @@ class ComponentViewer extends Component<Props, ComponentViewerState> {
102104
<div className="rcv-toolbar-panel">
103105
<Toolbar
104106
questionMarkToggledOn={isHelpOn}
107+
dropDowns={dropDowns}
108+
selectedItems={selectedToolbarItems}
105109
onQuestionMarkClick={this.onQuestionMarkToggle}
106-
dropDownLabel={dropDown ? dropDown.label : undefined}
107-
dropDownSelected={selectedToolbarItem}
108-
dropDownItems={dropDown ? dropDown.items : undefined}
109110
onDropDownItemSelection={this.selectToolbarItem}
110111
/>
111112
</div>
@@ -171,14 +172,33 @@ class ComponentViewer extends Component<Props, ComponentViewerState> {
171172
}
172173

173174
triggerDropDownSelection() {
174-
const {dropDown} = this.props;
175-
if (!dropDown) {
175+
const {dropDowns} = this.props;
176+
if (dropDowns.length === 0) {
176177
return;
177178
}
178179

179-
const {selectedToolbarItem} = this.state;
180-
if (selectedToolbarItem.length > 0) {
181-
dropDown.onSelect(selectedToolbarItem);
180+
const {selectedToolbarItems} = this.state;
181+
Object.keys(selectedToolbarItems).forEach(dropDownKey => {
182+
const dropDown = dropDownByLabelKey(dropDownKey);
183+
const itemKey = selectedToolbarItems[dropDownKey];
184+
if (!dropDown) {
185+
return;
186+
}
187+
188+
const itemLabel = dropItemLabelByKey();
189+
if (itemLabel) {
190+
dropDown.onSelect(itemLabel);
191+
}
192+
193+
function dropItemLabelByKey() {
194+
const found = dropDown!.items.filter(item => labelToKey(item.label) === itemKey);
195+
return found.length > 0 ? found[0].label : undefined;
196+
}
197+
});
198+
199+
function dropDownByLabelKey(labelKey: string) {
200+
const found = dropDowns.filter(dd => labelToKey(dd.label) === labelKey);
201+
return found.length ? found[0] : undefined;
182202
}
183203
}
184204

@@ -189,14 +209,16 @@ class ComponentViewer extends Component<Props, ComponentViewerState> {
189209
private dropDownKeyBoundActions() {
190210
const result: HotKeyBoundActions = {};
191211

192-
const {dropDown} = this.props;
193-
if (!dropDown) {
212+
const {dropDowns} = this.props;
213+
if (dropDowns.length === 0) {
194214
return result;
195215
}
196216

197-
dropDown.items
198-
.filter(item => !!item.hotKey)
199-
.forEach(item => result[item.hotKey!] = () => this.selectToolbarItem(item.label));
217+
dropDowns.forEach(dropDown =>
218+
dropDown.items
219+
.filter(item => !!item.hotKey)
220+
.forEach(item => result[item.hotKey!] =
221+
() => this.selectToolbarItem(dropDown.label, item.label)));
200222

201223
return result;
202224
}
@@ -272,7 +294,8 @@ class ComponentViewer extends Component<Props, ComponentViewerState> {
272294
this.pushUrl({
273295
registryName,
274296
demoName: '',
275-
entryTitle: ''});
297+
entryTitle: ''
298+
});
276299
}
277300

278301
private selectDemo = (demoName: string) => {
@@ -283,13 +306,20 @@ class ComponentViewer extends Component<Props, ComponentViewerState> {
283306
this.pushUrl({entryTitle: title});
284307
}
285308

286-
private selectToolbarItem = (label: string) => {
287-
const {dropDown} = this.props;
309+
private selectToolbarItem = (dropDownLabel: string, itemLabel: string) => {
310+
const {dropDowns} = this.props;
311+
const {selectedToolbarItems} = this.state;
288312

289-
this.pushUrl({selectedToolbarItem: label});
313+
this.pushUrl({
314+
selectedToolbarItems: {
315+
...selectedToolbarItems,
316+
[labelToKey(dropDownLabel)]: labelToKey(itemLabel)
317+
}
318+
});
290319

291-
if (dropDown) {
292-
dropDown.onSelect(label);
320+
const found = dropDowns.filter(dd => dd.label === dropDownLabel);
321+
if (found.length > 0) {
322+
found[0].onSelect(itemLabel);
293323
}
294324
}
295325

src/components/viewer/ComponentViewerState.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ export interface ComponentViewerState {
55
isFullScreen: boolean;
66
isHelpOn: boolean;
77
filterText: string;
8-
selectedToolbarItem: string;
8+
selectedToolbarItems: {[labelKey: string]: string};
99
}

src/components/viewer/ComponentViewerStateCreator.test.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe('ComponentViewerStateCreator', () => {
2626
filterText: '',
2727
isFullScreen: false,
2828
isHelpOn: false,
29-
selectedToolbarItem: ''
29+
selectedToolbarItems: {}
3030
});
3131
});
3232

@@ -46,9 +46,21 @@ describe('ComponentViewerStateCreator', () => {
4646
isFullScreen: true,
4747
isHelpOn: false,
4848
filterText: '',
49-
selectedToolbarItem: ''
49+
selectedToolbarItems: {brand: 'brand_one', services: 'fake_services'}
5050
});
5151

52-
expect(url).toEqual('_rcv_rname=core&_rcv_dname=demo-name&_rcv_fs=true');
52+
expect(url).toEqual('_rcv_rname=core&_rcv_dname=demo-name&_rcv_fs=true' +
53+
'&_rcv_dropdown_brand=brand_one&_rcv_dropdown_services=fake_services');
54+
});
55+
56+
it('should extract toolbar items from url', () => {
57+
const stateCreator = new ComponentViewerStateCreator(registries);
58+
const state = stateCreator.stateFromUrl(
59+
'/path',
60+
'_rcv_rname=core&_rcv_dropdown_brand=brand_one&_rcv_dropdown_services=fake_services');
61+
62+
expect(state.selectedToolbarItems).toEqual({
63+
brand: 'brand_one',
64+
services: 'fake_services'});
5365
});
5466
});

src/components/viewer/ComponentViewerStateCreator.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const queryParamNames = {
88
registryName: '_rcv_rname',
99
demoName: '_rcv_dname',
1010
entryTitle: '_rcv_title',
11-
selectedToolbarItem: '_rcv_titem',
11+
selectedToolbarItemPrefix: '_rcv_dropdown_',
1212
isFullScreen: '_rcv_fs',
1313
isHelpOn: '_rcv_help'
1414
};
@@ -32,9 +32,9 @@ export class ComponentViewerStateCreator {
3232
stateFromUrl(path: string, search: string): ComponentViewerState {
3333
const searchParams = new URLSearchParams(search);
3434

35-
const selectedToolbarItem = searchParams.get(queryParamNames.selectedToolbarItem) || '';
35+
const selectedToolbarItems = extractSelectedToolbarItems();
3636

37-
const miniAppByUrl = this.miniAppByUrl(path + search);
37+
const miniAppByUrl = this.miniAppByUrl(path + '?' + search);
3838
if (miniAppByUrl) {
3939
return {
4040
registryName: miniAppByUrl.registry.name,
@@ -43,7 +43,7 @@ export class ComponentViewerStateCreator {
4343
isFullScreen: true,
4444
isHelpOn: false,
4545
filterText: '',
46-
selectedToolbarItem
46+
selectedToolbarItems
4747
};
4848
}
4949

@@ -68,9 +68,22 @@ export class ComponentViewerStateCreator {
6868
entryTitle,
6969
isFullScreen,
7070
isHelpOn,
71-
selectedToolbarItem,
71+
selectedToolbarItems,
7272
filterText: ''
7373
};
74+
75+
function extractSelectedToolbarItems(): {[key: string]: string} {
76+
const result = {};
77+
78+
// @ts-ignore
79+
Array.from(searchParams.keys()).forEach((k: string) => {
80+
if (k.indexOf(queryParamNames.selectedToolbarItemPrefix) === 0) {
81+
result[k.substring(queryParamNames.selectedToolbarItemPrefix.length)] = searchParams.get(k);
82+
}
83+
});
84+
85+
return result;
86+
}
7487
}
7588

7689
buildUrlSearchParams(state: ComponentViewerState): string {
@@ -79,7 +92,11 @@ export class ComponentViewerStateCreator {
7992
Object.keys(state).forEach(k => {
8093
const v = state[k];
8194

82-
if (v) {
95+
if (k === 'selectedToolbarItems') {
96+
const toolbarItems: {[labelKey: string]: string} = v;
97+
Object.keys(v).forEach(dropDownLabelKey => searchParams.set(
98+
queryParamNames.selectedToolbarItemPrefix + dropDownLabelKey, toolbarItems[dropDownLabelKey]));
99+
} else if (v) {
83100
searchParams.set(queryParamNames[k], v.toString());
84101
}
85102
});
Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,40 @@
11
import * as React from 'react';
22

33
import { ToolbarDropDown } from './ToolbarDropDown';
4-
import { ToolbarDropDownItem } from './ToolbarDropDownItem';
4+
5+
import { ComponentViewerDropDown } from '../ComponentViewerDropDown';
6+
7+
import { labelToKey } from './labelUtils';
58

69
import './Toolbar.css';
710

811
export interface Props {
9-
dropDownLabel?: string;
10-
dropDownItems?: ToolbarDropDownItem[];
11-
dropDownSelected?: string;
12+
dropDowns: ComponentViewerDropDown[];
13+
selectedItems: {[label: string]: string};
1214
questionMarkToggledOn: boolean;
13-
onDropDownItemSelection?(label: string): void;
15+
onDropDownItemSelection?(dropDownLabel: string, itemLabel: string): void;
1416
onQuestionMarkClick(): void;
1517
}
1618

1719
export class Toolbar extends React.PureComponent<Props> {
1820
render() {
1921
const {
20-
dropDownLabel,
21-
dropDownItems,
22-
dropDownSelected,
22+
dropDowns,
23+
selectedItems,
2324
questionMarkToggledOn,
2425
onQuestionMarkClick
2526
} = this.props;
2627

2728
const questionMarkClassName = 'rcv-toolbar-action' + (questionMarkToggledOn ? ' on' : '');
2829
return (
2930
<div className="rcv-toolbar">
30-
{dropDownItems && dropDownItems.length > 0 && <ToolbarDropDown
31-
label={dropDownLabel || ''}
32-
items={dropDownItems}
33-
selectedLabel={dropDownSelected}
31+
{dropDowns.map(dropDown => <ToolbarDropDown
32+
key={dropDown.label}
33+
label={dropDown.label}
34+
items={dropDown.items}
35+
selectedLabelKey={selectedItems[labelToKey(dropDown.label)]}
3436
onItemSelect={this.onDropDownItemSelection}
35-
/>}
37+
/>)}
3638
<div
3739
className={questionMarkClassName}
3840
onClick={onQuestionMarkClick}
@@ -43,10 +45,10 @@ export class Toolbar extends React.PureComponent<Props> {
4345
);
4446
}
4547

46-
private onDropDownItemSelection = (label: string) => {
48+
private onDropDownItemSelection = (dropDownLabel: string, itemLabel: string) => {
4749
const {onDropDownItemSelection} = this.props;
4850
if (onDropDownItemSelection) {
49-
onDropDownItemSelection(label);
51+
onDropDownItemSelection(dropDownLabel, itemLabel);
5052
}
5153
}
5254
}

0 commit comments

Comments
 (0)