Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Open sidebar
share
fe-common-library
Commits
68bb04d1
Commit
68bb04d1
authored
5 years ago
by
Jim Lin
Browse files
Options
Download
Email Patches
Plain Diff
[
ANUE-1807
] Add AnueSearchInput component
parent
cb10707e
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
519 additions
and
0 deletions
+519
-0
src/components/AnueSearchInput/AnueSearchInput.js
src/components/AnueSearchInput/AnueSearchInput.js
+127
-0
src/components/AnueSearchInput/AnueSearchInput.scss
src/components/AnueSearchInput/AnueSearchInput.scss
+111
-0
src/components/AnueSearchInput/__stories__/AnueSearchInput.md
...components/AnueSearchInput/__stories__/AnueSearchInput.md
+28
-0
src/components/AnueSearchInput/__stories__/AnueSearchInput.stories.js
...ts/AnueSearchInput/__stories__/AnueSearchInput.stories.js
+145
-0
src/components/AnueSearchInput/__tests__/AnueSearchInput.test.js
...ponents/AnueSearchInput/__tests__/AnueSearchInput.test.js
+108
-0
No files found.
src/components/AnueSearchInput/AnueSearchInput.js
0 → 100644
View file @
68bb04d1
import
React
from
'
react
'
;
import
PropTypes
from
'
prop-types
'
;
import
classnames
from
'
classnames/bind
'
;
import
styles
from
'
./AnueSearchInput.scss
'
;
const
cx
=
classnames
.
bind
(
styles
);
class
AnueSearchInput
extends
React
.
Component
{
static
propTypes
=
{
customAttribute
:
PropTypes
.
objectOf
(
PropTypes
.
any
),
customWrapperStyles
:
PropTypes
.
objectOf
(
PropTypes
.
any
),
customInputStyles
:
PropTypes
.
objectOf
(
PropTypes
.
any
),
customIconStyles
:
PropTypes
.
objectOf
(
PropTypes
.
any
),
customInputClassName
:
PropTypes
.
string
,
shouldAlwaysDisplayInput
:
PropTypes
.
bool
,
onIconClick
:
PropTypes
.
func
,
onInputChange
:
PropTypes
.
func
,
onInputFocus
:
PropTypes
.
func
,
onInputBlur
:
PropTypes
.
func
,
onInputPressEnter
:
PropTypes
.
func
,
onDisplayChange
:
PropTypes
.
func
,
theme
:
PropTypes
.
string
,
placeholder
:
PropTypes
.
string
,
defaultValue
:
PropTypes
.
string
,
};
static
defaultProps
=
{
customAttribute
:
null
,
customWrapperStyles
:
null
,
customInputStyles
:
null
,
customIconStyles
:
null
,
shouldAlwaysDisplayInput
:
false
,
theme
:
null
,
// mobile | desktop
placeholder
:
'
搜尋新聞、代碼或名稱
'
,
defaultValue
:
null
,
customInputClassName
:
null
,
onDisplayChange
:
()
=>
{},
onIconClick
:
()
=>
{},
onInputChange
:
()
=>
{},
onInputFocus
:
()
=>
{},
onInputBlur
:
()
=>
{},
onInputPressEnter
:
()
=>
{},
};
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
value
:
props
.
defaultValue
||
''
,
shouldDisplayInput
:
props
.
shouldAlwaysDisplayInput
||
props
.
theme
!==
'
mobile
'
,
};
}
handleChange
=
e
=>
{
const
{
onInputChange
}
=
this
.
props
;
const
value
=
e
.
target
.
value
;
this
.
setState
({
value
,
});
onInputChange
(
value
);
};
handleClickIcon
=
()
=>
{
const
{
onIconClick
,
shouldAlwaysDisplayInput
,
onDisplayChange
}
=
this
.
props
;
const
{
value
,
shouldDisplayInput
}
=
this
.
state
;
onIconClick
(
value
);
if
(
!
shouldAlwaysDisplayInput
)
{
this
.
setState
({
shouldDisplayInput
:
!
shouldDisplayInput
,
});
onDisplayChange
(
!
shouldDisplayInput
);
}
};
handleKeyDown
=
e
=>
{
if
(
e
.
keyCode
===
13
)
{
const
{
onInputPressEnter
}
=
this
.
props
;
const
{
value
}
=
this
.
state
;
if
(
value
)
{
onInputPressEnter
(
value
);
}
}
};
handleResetValue
=
()
=>
{
this
.
setState
({
value
:
''
,
});
};
render
()
{
const
{
placeholder
,
onInputFocus
,
onInputBlur
,
customAttribute
,
customWrapperStyles
,
customInputStyles
,
customIconStyles
,
theme
,
customInputClassName
,
}
=
this
.
props
;
const
{
shouldDisplayInput
}
=
this
.
state
;
return
(
<
div
className
=
{
cx
(
'
anue-search-input--wrapper
'
,
theme
,
customInputClassName
)}
style
=
{
customWrapperStyles
}
>
<
input
className
=
{
cx
(
'
anue-search-input
'
,
theme
,
{
display
:
shouldDisplayInput
,
})}
onChange
=
{
this
.
handleChange
}
onFocus
=
{
onInputFocus
}
onBlur
=
{
onInputBlur
}
onKeyDown
=
{
this
.
handleKeyDown
}
style
=
{
customInputStyles
}
placeholder
=
{
placeholder
}
{...
customAttribute
}
/
>
<
div
className
=
{
cx
(
'
search-icon
'
,
theme
)}
onClick
=
{
this
.
handleClickIcon
}
style
=
{
customIconStyles
}
/
>
<
/div
>
);
}
}
export
default
AnueSearchInput
;
This diff is collapsed.
Click to expand it.
src/components/AnueSearchInput/AnueSearchInput.scss
0 → 100644
View file @
68bb04d1
@import
"../../styles/vars"
;
%search-icon-bg
{
background
:
url("../../assets/icon-search.svg")
no-repeat
;
background-size
:
cover
;
background-position
:
center
;
}
.anue-search-input--wrapper
{
width
:
100%
;
position
:
relative
;
display
:
flex
;
flex-direction
:
row
;
justify-content
:
flex-end
;
align-items
:
center
;
z-index
:
5
;
&
.test1
{
color
:
#fff
;
}
&
.test2
{
color
:
#000
;
}
.anue-search-input
{
position
:
relative
;
height
:
30px
;
background-color
:
$gray-eeeeee
;
border-radius
:
2px
;
font-size
:
13px
;
transition
:
flex
0
.3s
$cubicEaseInAndOut
,
width
0
.3s
$cubicEaseInAndOut
;
@media
only
screen
and
(
max-width
:
$screenSizeLg
)
{
flex
:
0
;
width
:
0
;
padding
:
0
;
&
.display
{
flex
:
1
;
width
:
100%
;
padding
:
0
8px
;
}
}
@media
only
screen
and
(
min-width
:
$screenSizeLg
)
{
width
:
400px
;
height
:
32px
;
padding
:
7px
8px
;
border
:
1px
solid
transparent
;
transition
:
flex
0
.3s
$cubicEaseInAndOut
,
width
0
.3s
$cubicEaseInAndOut
,
background-color
0
.2s
$cubicEaseInAndOut
,
border
0
.2s
$cubicEaseInAndOut
;
}
&
.desktop
{
width
:
400px
;
height
:
32px
;
padding
:
7px
8px
;
border
:
1px
solid
transparent
;
&
:focus
{
background-color
:
$white
;
border
:
1px
solid
$gray-eeeeee
;
}
}
&
.mobile
{
flex
:
0
;
width
:
0
;
padding
:
0
;
&
.display
{
flex
:
1
;
width
:
100%
;
padding
:
0
8px
;
}
}
&
::placeholder
{
padding
:
0
8px
;
}
}
.search-icon
{
width
:
18px
;
height
:
18px
;
margin
:
0
12px
;
cursor
:
pointer
;
@extend
%search-icon-bg
;
&
:not
(
.mobile
)
{
@media
only
screen
and
(
min-width
:
$screenSizeLg
)
{
position
:
absolute
;
right
:
0
;
top
:
8px
;
width
:
16px
;
height
:
16px
;
z-index
:
5
;
}
}
&
.desktop
{
position
:
absolute
;
right
:
0
;
top
:
8px
;
width
:
16px
;
height
:
16px
;
z-index
:
5
;
}
}
}
This diff is collapsed.
Click to expand it.
src/components/AnueSearchInput/__stories__/AnueSearchInput.md
0 → 100644
View file @
68bb04d1
## Usage
```
import AnueSearchInput from 'fe-common-library/dest/components/AnueSearchInput/AnueSearchInput';
import 'fe-common-library/dest/components/AnueSearchInput/style.css';
const defaultProps = {
customAttribute: null,
customWrapperStyles: null,
customInputStyles: null,
customIconStyles: null,
shouldAlwaysDisplayInput: false,
theme: null, // mobile | desktop
placeholder: '搜尋新聞、代碼或名稱',
defaultValue: null,
onIconClick: () => {},
onInputChange: () => {},
onInputFocus: () => {},
onInputBlur: () => {},
onInputPressEnter: () => {},
}
render() {
return (
<AnueSearchInput {...defaultProps}/>;
);
}
```
This diff is collapsed.
Click to expand it.
src/components/AnueSearchInput/__stories__/AnueSearchInput.stories.js
0 → 100644
View file @
68bb04d1
/* eslint-disable import/no-extraneous-dependencies */
import
React
from
'
react
'
;
import
{
storiesOf
}
from
'
@storybook/react
'
;
import
{
withInfo
}
from
'
@storybook/addon-info
'
;
import
{
withNotes
}
from
'
@storybook/addon-notes
'
;
import
{
withState
}
from
'
@dump247/storybook-state
'
;
import
*
as
knobs
from
'
@storybook/addon-knobs/react
'
;
import
Header
from
'
../../Header/Header
'
;
import
AnueSearchInput
from
'
../AnueSearchInput
'
;
import
{
MobileMenu
}
from
'
../../MobileHeader
'
;
import
AccountMenu
from
'
../../AccountMenu/AccountMenu
'
;
import
AccountMenuStyles
from
'
../../AccountMenu/__stories__/Container.scss
'
;
import
defaultNotes
from
'
./AnueSearchInput.md
'
;
const
WRAPPER_STYLES
=
{
width
:
'
100vw
'
,
height
:
'
100vh
'
,
display
:
'
flex
'
,
justifyContent
:
'
flex-start
'
,
alignItems
:
'
center
'
,
flexDirection
:
'
column
'
,
};
const
MOBILE_WRAPPER_STYLES
=
{
...
WRAPPER_STYLES
,
backgroundColor
:
'
rgba(0, 0, 0, 0.5)
'
,
};
const
MOBILE_MENU_SEARCH_ICON_WRAPPER
=
{
position
:
'
absolute
'
,
right
:
0
,
top
:
0
,
height
:
'
44px
'
,
display
:
'
flex
'
,
justifyContent
:
'
center
'
,
alignItems
:
'
center
'
,
zIndex
:
5
,
};
const
stories
=
storiesOf
(
'
AnueSearchInput
'
,
module
);
stories
.
addDecorator
(
knobs
.
withKnobs
);
stories
.
add
(
'
Desktop
'
,
withState
({
hiddenInput
:
false
,
isFocus
:
false
,
})(
withNotes
(
defaultNotes
)(
withInfo
()(()
=>
{
knobs
.
boolean
(
'
onFocus
'
,
false
);
knobs
.
text
(
'
Input Value
'
,
''
);
return
(
<
div
style
=
{
WRAPPER_STYLES
}
>
<
Header
channel
=
"
全站搜尋Bar
"
displayChannelName
customMenu
=
{
<
AccountMenu
/>
}
searchBar
=
{
<
AnueSearchInput
onInputChange
=
{
e
=>
{
const
value
=
e
.
target
.
value
;
knobs
.
text
(
'
Input Value
'
,
value
);
}}
onInputFocus
=
{()
=>
{
knobs
.
boolean
(
'
onFocus
'
,
true
);
}}
customAttribute
=
{{
onBlur
:
()
=>
{
knobs
.
boolean
(
'
onFocus
'
,
false
);
},
}}
/
>
}
/
>
<
/div
>
);
})
)
)
);
stories
.
add
(
'
Mobile
'
,
withState
({
hiddenInput
:
true
,
isFocus
:
false
,
})(
withNotes
(
defaultNotes
)(
withInfo
()(({
store
})
=>
{
knobs
.
boolean
(
'
onFocus
'
,
false
);
knobs
.
text
(
'
Input Value
'
,
''
);
return
(
<
div
style
=
{
MOBILE_WRAPPER_STYLES
}
>
<
div
className
=
{
AccountMenuStyles
.
mobileWrapper
}
style
=
{{
backgroundColor
:
'
#fff
'
,
height
:
'
100vh
'
}}
>
<
header
className
=
{
AccountMenuStyles
.
mobileHeader
}
style
=
{{
borderBottom
:
'
1px solid #eee
'
}}
>
<
MobileMenu
/>
<
nav
className
=
{
AccountMenuStyles
.
mobileLinks
}
>
<
a
>
外匯
<
/a
>
<
a
>
市場
<
/a
>
<
a
>
虛擬貨幣
<
/a
>
<
/nav
>
<
div
className
=
{
AccountMenuStyles
.
accountWrapper
}
>
<
AccountMenu
highlight
isLogin
/>
<
/div
>
<
div
style
=
{{
...
MOBILE_MENU_SEARCH_ICON_WRAPPER
,
width
:
'
100%
'
,
}}
>
<
AnueSearchInput
theme
=
"
mobile
"
hiddenInput
=
{
store
.
state
.
hiddenInput
}
onIconClick
=
{()
=>
{
store
.
set
({
hiddenInput
:
!
store
.
state
.
hiddenInput
});
}}
onInputChange
=
{
value
=>
{
knobs
.
text
(
'
Input Value
'
,
value
);
}}
onInputFocus
=
{()
=>
{
knobs
.
boolean
(
'
onFocus
'
,
true
);
}}
customAttribute
=
{{
onBlur
:
()
=>
{
knobs
.
boolean
(
'
onFocus
'
,
false
);
},
}}
customInputStyles
=
{{
maxWidth
:
'
calc(100% - 85px)
'
,
}}
/
>
<
/div
>
<
/header
>
<
/div
>
<
/div
>
);
})
)
)
);
This diff is collapsed.
Click to expand it.
src/components/AnueSearchInput/__tests__/AnueSearchInput.test.js
0 → 100644
View file @
68bb04d1
import
React
from
'
react
'
;
import
{
mount
}
from
'
enzyme
'
;
import
AnueSearchInput
from
'
../AnueSearchInput
'
;
describe
(
'
<AnueSearchInput /> component
'
,
()
=>
{
let
makeSubject
;
const
defaultProps
=
{
customAttribute
:
null
,
customWrapperStyles
:
null
,
customInputStyles
:
null
,
customIconStyles
:
null
,
shouldAlwaysDisplayInput
:
false
,
theme
:
null
,
// mobile | desktop
placeholder
:
'
搜尋新聞、代碼或名稱
'
,
defaultValue
:
null
,
onIconClick
:
jest
.
fn
(),
onInputChange
:
jest
.
fn
(),
onInputFocus
:
jest
.
fn
(),
onInputBlur
:
jest
.
fn
(),
onInputPressEnter
:
jest
.
fn
(),
};
beforeEach
(()
=>
{
jest
.
resetModules
();
jest
.
resetAllMocks
();
makeSubject
=
params
=>
mount
(
<
AnueSearchInput
{...
defaultProps
}
{...
params
}
/>
)
;
});
it
(
'
should display the search input and the search icon both
'
,
()
=>
{
const
subject
=
makeSubject
();
expect
(
subject
.
find
(
'
.anue-search-input.display
'
).
length
).
toBe
(
1
);
});
describe
(
'
When the prop theme is mobile
'
,
()
=>
{
let
subject
;
beforeEach
(()
=>
{
subject
=
makeSubject
({
theme
:
'
mobile
'
,
});
});
it
(
'
should display the search icon only on the initial render.
'
,
()
=>
{
expect
(
subject
.
find
(
'
.anue-search-input.display
'
).
length
).
toBe
(
0
);
});
it
(
'
should toggle the search input only on the initial render if the search icon is clicked
'
,
()
=>
{
const
icon
=
subject
.
find
(
'
.search-icon
'
);
expect
(
subject
.
find
(
'
.anue-search-input.display
'
).
length
).
toBe
(
0
);
icon
.
simulate
(
'
click
'
);
expect
(
subject
.
find
(
'
.anue-search-input.display
'
).
length
).
toBe
(
1
);
icon
.
simulate
(
'
click
'
);
expect
(
subject
.
find
(
'
.anue-search-input.display
'
).
length
).
toBe
(
0
);
});
it
(
'
should not hide the input even the props.theme is mobile if the props.shouldAlwaysDisplayInput is true
'
,
()
=>
{
subject
=
makeSubject
({
theme
:
'
mobile
'
,
shouldAlwaysDisplayInput
:
true
,
});
const
icon
=
subject
.
find
(
'
.search-icon
'
);
expect
(
subject
.
find
(
'
.anue-search-input.display
'
).
length
).
toBe
(
1
);
icon
.
simulate
(
'
click
'
);
expect
(
subject
.
find
(
'
.anue-search-input.display
'
).
length
).
toBe
(
1
);
});
});
describe
(
'
The actions to call the private methods with the props
'
,
()
=>
{
let
subject
;
beforeEach
(()
=>
{
subject
=
makeSubject
({
theme
:
'
mobile
'
,
});
});
it
(
'
should call props.onInputChange and pass the input value
'
,
()
=>
{
subject
=
makeSubject
();
subject
.
find
(
'
.anue-search-input
'
).
simulate
(
'
change
'
,
{
target
:
{
value
:
'
test
'
}
});
expect
(
defaultProps
.
onInputChange
).
toHaveBeenCalledWith
(
'
test
'
);
});
it
(
'
should call props.onIconClick and pass the input value
'
,
()
=>
{
subject
=
makeSubject
();
subject
.
find
(
'
.anue-search-input
'
).
simulate
(
'
change
'
,
{
target
:
{
value
:
'
test
'
}
});
subject
.
find
(
'
.search-icon
'
).
simulate
(
'
click
'
);
expect
(
defaultProps
.
onIconClick
).
toHaveBeenCalledWith
(
'
test
'
);
});
it
(
'
should call props.onInputPressEnter and pass the input value
'
,
()
=>
{
subject
=
makeSubject
();
const
input
=
subject
.
find
(
'
.anue-search-input
'
);
input
.
simulate
(
'
change
'
,
{
target
:
{
value
:
'
test
'
}
});
input
.
simulate
(
'
keyDown
'
,
{
keyCode
:
13
});
expect
(
defaultProps
.
onInputPressEnter
).
toHaveBeenCalledWith
(
'
test
'
);
});
});
});
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment