Skip to content

Commit a2b4319

Browse files
authored
feat(editor): ✨ add scope picker (#17)
- Button alignment changed to accommodate for extra button. - Redux state refactored for better structure. Resolves #3
1 parent 1ea12a9 commit a2b4319

19 files changed

+502
-99
lines changed

components/common/recent-list-item.component.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ const styles = (theme: CommitComposerTheme) => ({
2626
export interface OwnProps {
2727
itemClassName?: string;
2828
item: string;
29-
title: string;
30-
display?: JSX.Element;
29+
tooltip?: string;
30+
display: JSX.Element;
3131
onClick?: (item: string) => void;
3232
}
3333
export interface ReduxProps {}
@@ -37,10 +37,14 @@ export interface State {}
3737

3838
class RecentListItemComponent extends React.Component<Props, State> {
3939
render(): JSX.Element {
40-
const { classes, display, item, itemClassName, title, onClick } = this.props;
40+
const { classes, display, item, itemClassName, tooltip, onClick } = this.props;
4141

4242
return (
43-
<Tooltip overlayClassName={classes.tooltip} title={title} mouseLeaveDelay={0}>
43+
<Tooltip
44+
mouseEnterDelay={1.5}
45+
overlayClassName={classes.tooltip}
46+
title={tooltip}
47+
mouseLeaveDelay={0}>
4448
<Col
4549
className={classNames(classes.itemContainer, itemClassName)}
4650
onClick={() => onClick?.(item)}>

components/common/recent-list.component.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import RecentListItemComponent from './recent-list-item.component';
77

88
export type RecentItem = {
99
item: string;
10-
title: string;
11-
display?: JSX.Element;
10+
tooltip?: string;
11+
display: JSX.Element;
1212
};
1313

1414
const styles = {
@@ -50,7 +50,7 @@ class RecentListComponent extends React.Component<Props, State> {
5050
itemClassName={itemClassName}
5151
key={x.item}
5252
item={x.item}
53-
title={x.title}
53+
tooltip={x.tooltip}
5454
display={x.display}
5555
onClick={(item) => onClick?.(item)}
5656
/>

components/common/searchable-menu.component.tsx

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,17 @@ class SearchableMenuComponent extends React.Component<Props, State> {
7777
}
7878

7979
componentDidUpdate(prevProps: Readonly<Props>): void {
80-
if (!prevProps.focus && this.props.focus) {
80+
const { focus, items } = this.props;
81+
82+
if (!prevProps.focus && focus) {
8183
this.focus();
8284
}
85+
86+
if (prevProps.items !== items) {
87+
this.setState({
88+
visibleItems: this.renderItems(items),
89+
});
90+
}
8391
}
8492

8593
focus(): void {
@@ -124,6 +132,10 @@ class SearchableMenuComponent extends React.Component<Props, State> {
124132
const { classes } = this.props;
125133

126134
const highlight = (input: string, regex?: RegExp): { elem: JSX.Element; found: boolean } => {
135+
if (input === '' || input === undefined) {
136+
return;
137+
}
138+
127139
const parts: React.ReactNode[] = input.split(regex);
128140
let found = false;
129141

@@ -152,17 +164,20 @@ class SearchableMenuComponent extends React.Component<Props, State> {
152164
const descriptionRegex = query ? new RegExp(`(${escapeRegExp(query)})`, 'gi') : undefined;
153165
const description = highlight(x.description, descriptionRegex);
154166

155-
const found = query === undefined || query === '' || title.found || description.found;
167+
const found =
168+
query === undefined || query === '' || title.found || (description && description.found);
156169

157-
description.elem = React.cloneElement(description.elem, {
158-
type: 'secondary',
159-
className: classes.description,
160-
});
170+
if (description) {
171+
description.elem = React.cloneElement(description.elem, {
172+
type: 'secondary',
173+
className: classes.description,
174+
});
175+
}
161176

162177
return {
163178
item: x.item,
164179
title: title.elem,
165-
description: description.elem,
180+
description: description && description.elem,
166181
icon: x.icon,
167182
found,
168183
};

components/editor/editor.component.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,13 @@ const styles = (theme: CommitComposerTheme) => ({
2929
},
3030
},
3131
buttons: {
32+
position: 'absolute',
33+
top: 0,
34+
right: 0,
3235
padding: 8,
33-
width: '100%',
36+
height: '100%',
3437
justifyContent: 'flex-end',
38+
alignItems: 'flex-end',
3539
},
3640
});
3741

@@ -70,7 +74,9 @@ class EditorComponent extends React.Component<Props, State> {
7074
allowClear
7175
/>
7276

73-
<Space className={classes.buttons}>{children}</Space>
77+
<Space direction="vertical" className={classes.buttons}>
78+
{children}
79+
</Space>
7480
</div>
7581
);
7682
}
Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import { defineAction, defineScenarioAction } from 'redux-typed-actions';
2-
import { GitmojiDefinition } from 'shared/presets/gitmojis';
3-
import { TypeDefinition } from 'shared/presets/types';
42
import { ValidationResult } from 'state';
53

64
export const EditorUpdatedAction = defineAction<string>('EditorUpdatedAction');
@@ -9,6 +7,3 @@ export const ValidationUpdatedAsync = defineScenarioAction<undefined, Validation
97
);
108
export const EditorFormatAction = defineAction('EditorFormatAction');
119
export const EditorLoadAction = defineAction<boolean>('EditorLoadAction');
12-
export const GitmojiSelectAction = defineAction<GitmojiDefinition>('GitmojiSelectAction');
13-
export const TypeSelectAction = defineAction<TypeDefinition>('TypeSelectAction');
14-
export const ToggleShortcodeAction = defineAction<boolean>('ToggleShortcodeAction');

components/editor/state/editor.epic.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
1+
import { GitmojiSelectAction, TypeSelectAction } from 'components/preset/state/preset.action';
12
import { Epic } from 'redux-observable';
23
import { PlainAction } from 'redux-typed-actions';
34
import { concat, of } from 'rxjs';
45
import { debounceTime, switchMap } from 'rxjs/operators';
56
import { requestValidation } from 'shared/api';
67
import { AppState } from 'state';
78

8-
import {
9-
EditorFormatAction,
10-
EditorLoadAction,
11-
EditorUpdatedAction,
12-
GitmojiSelectAction,
13-
TypeSelectAction,
14-
} from './editor.action';
9+
import { EditorFormatAction, EditorLoadAction, EditorUpdatedAction } from './editor.action';
1510

1611
export const updateValidation: Epic<PlainAction, PlainAction, AppState> = (action$, store$) =>
1712
action$

components/editor/state/editor.reducer.ts

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1+
import {
2+
GitmojiSelectAction,
3+
ScopeSelectAction,
4+
TypeSelectAction,
5+
} from 'components/preset/state/preset.action';
16
import { PlainAction } from 'redux-typed-actions';
27
import { CommitMessageLib } from 'shared/commit-message.lib';
3-
import { LRUCache } from 'shared/lru-cache';
4-
import { GitmojiDefinition } from 'shared/presets/gitmojis';
5-
import { TypeDefinition } from 'shared/presets/types';
68
import { AppState, EditorState } from 'state';
79

810
import {
911
EditorFormatAction,
1012
EditorLoadAction,
1113
EditorUpdatedAction,
12-
GitmojiSelectAction,
13-
ToggleShortcodeAction,
14-
TypeSelectAction,
1514
ValidationUpdatedAsync,
1615
} from './editor.action';
1716

@@ -37,22 +36,17 @@ const editorReducer = (
3736
state.loading = action.payload;
3837
} else if (GitmojiSelectAction.is(action)) {
3938
const { payload } = action;
40-
state.editorValue = CommitMessageLib.setGitmoji(state.editorValue, payload, state.useShortcode);
41-
42-
const map = state.recentGitmojis.map((x) => ({ key: x.shortcode, value: x }));
43-
const cache = new LRUCache<GitmojiDefinition>(map, 20);
44-
cache.write(payload.shortcode, payload);
45-
state.recentGitmojis = cache.toArray();
39+
state.editorValue = CommitMessageLib.setGitmoji(
40+
state.editorValue,
41+
payload,
42+
appState.preset.useShortcode,
43+
);
4644
} else if (TypeSelectAction.is(action)) {
4745
const { payload } = action;
48-
state.editorValue = CommitMessageLib.setType(state.editorValue, action.payload);
49-
50-
const map = state.recentTypes.map((x) => ({ key: x.key, value: x }));
51-
const cache = new LRUCache<TypeDefinition>(map, 20);
52-
cache.write(payload.key, payload);
53-
state.recentTypes = cache.toArray();
54-
} else if (ToggleShortcodeAction.is(action)) {
55-
state.useShortcode = action.payload;
46+
state.editorValue = CommitMessageLib.setType(state.editorValue, payload);
47+
} else if (ScopeSelectAction.is(action)) {
48+
const { payload } = action;
49+
state.editorValue = CommitMessageLib.setScope(state.editorValue, payload);
5650
}
5751

5852
return state;

components/editor/gitmoji-picker.component.tsx renamed to components/preset/gitmoji-picker.component.tsx

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import { Button, Dropdown, Space, Switch, Typography } from 'antd';
22
import classNames from 'classnames';
3-
import RecentListComponent from 'components/common/recent-list.component';
3+
import RecentListComponent, { RecentItem } from 'components/common/recent-list.component';
44
import SearchableMenuComponent from 'components/common/searchable-menu.component';
55
import React from 'react';
66
import { AiOutlineCheck, AiOutlineClose, AiOutlineDown } from 'react-icons/ai';
77
import withStyles, { WithStylesProps } from 'react-jss';
88
import { connect, Dispatch } from 'react-redux';
99
import { GitmojiDefinition, GITMOJIS } from 'shared/presets/gitmojis';
10-
import { AppState, EditorState } from 'state';
10+
import { AppState, PresetState } from 'state';
1111

12-
import { GitmojiSelectAction, ToggleShortcodeAction } from './state/editor.action';
12+
import { GitmojiSelectAction, ToggleShortcodeAction } from './state/preset.action';
1313

1414
const styles = (theme: CommitComposerTheme) => ({
1515
menu: {
16+
border: `1px solid ${theme.lighter}`,
1617
display: 'block',
1718
[`@media only screen and (min-width: ${theme.screenMD})`]: {
1819
maxWidth: 380,
@@ -89,7 +90,7 @@ const styles = (theme: CommitComposerTheme) => ({
8990

9091
export interface OwnProps {}
9192
export interface ReduxProps {
92-
editor: EditorState;
93+
preset: PresetState;
9394
}
9495
export interface DispatchProps {
9596
gitmojiSelected: (gitmoji: GitmojiDefinition) => void;
@@ -129,7 +130,7 @@ class GitmojiPickerComponent extends React.Component<Props, State> {
129130
}
130131

131132
render(): JSX.Element {
132-
const { classes, editor, toggleShortcode } = this.props;
133+
const { classes, preset, toggleShortcode } = this.props;
133134
const { hovered, visible } = this.state;
134135

135136
const menu = (
@@ -138,21 +139,23 @@ class GitmojiPickerComponent extends React.Component<Props, State> {
138139
itemClassName={classes.recentItem}
139140
className={classes.recentList}
140141
onClick={(key) => this.handleClick(key)}
141-
items={editor.recentGitmojis.map((x) => ({
142-
item: x.shortcode,
143-
title: x.description,
144-
display: (
145-
<span aria-label={x.shortcode} role="img">
146-
{x.icon}
147-
</span>
148-
),
149-
}))}
142+
items={preset.recentGitmojis.map(
143+
(x): RecentItem => ({
144+
item: x.shortcode,
145+
tooltip: x.description,
146+
display: (
147+
<span aria-label={x.shortcode} role="img">
148+
{x.icon}
149+
</span>
150+
),
151+
}),
152+
)}
150153
/>
151154
<SearchableMenuComponent
152155
focus={visible}
153156
className={classes.items}
154157
searchBarClassName={classNames(classes.searchBar, {
155-
[classes.noTopPadding]: Boolean(editor.recentGitmojis?.length),
158+
[classes.noTopPadding]: Boolean(preset.recentGitmojis?.length),
156159
})}
157160
onClick={(key) => this.handleClick(key)}
158161
items={GITMOJIS.map((x) => ({
@@ -169,7 +172,7 @@ class GitmojiPickerComponent extends React.Component<Props, State> {
169172
<Typography.Text className={classes.actionText}>Shortcode:</Typography.Text>
170173
<Switch
171174
size="small"
172-
defaultChecked={editor.useShortcode}
175+
defaultChecked={preset.useShortcode}
173176
checkedChildren={<AiOutlineCheck />}
174177
unCheckedChildren={<AiOutlineClose />}
175178
onChange={(x) => toggleShortcode(x)}
@@ -208,8 +211,8 @@ class GitmojiPickerComponent extends React.Component<Props, State> {
208211
}
209212

210213
function mapStateToProps(state: AppState): ReduxProps {
211-
const { editor } = state;
212-
return { editor };
214+
const { preset } = state;
215+
return { preset };
213216
}
214217

215218
function mapDispatchToProps(dispatch: Dispatch): DispatchProps {

0 commit comments

Comments
 (0)