[SearchPanel] 액션 추가/VirtualGridList 적용/컴포넌트화

This commit is contained in:
hyunwoo93.cha
2024-02-02 01:14:11 +09:00
parent 35f17bb974
commit fa333b5b35
22 changed files with 585 additions and 20 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -43,6 +43,7 @@ export const types = {
// search actions
GET_SEARCH: "GET_SEARCH",
RESET_SEARCH: "RESET_SEARCH",
GET_SEARCH_PROCESSED: "GET_SEARCH_PROCESSED",
// event actions
GET_WELCOME_EVENT_INFO: "GET_WELCOME_EVENT_INFO",

View File

@@ -13,6 +13,21 @@ export const getSearch = (params) => (dispatch, getState) => {
type: types.GET_SEARCH,
payload: response.data,
});
const processedData = response.data.data.result.results.reduce(
(acc, current) => {
acc.data[current.type] = current.docs;
acc.totalCount[current.type] = current.total_count;
return acc;
},
{ data: {}, totalCount: {} }
);
dispatch({
type: types.GET_SEARCH_PROCESSED,
payload: processedData,
});
};
const onFail = (error) => {

View File

@@ -2,6 +2,10 @@ import { types } from "../actions/actionTypes";
const initialState = {
searchDatas: {},
processedDatas: {
data: {},
totalCount: {},
},
searchPerformed: false,
};
@@ -14,6 +18,13 @@ export const searchReducer = (state = initialState, action) => {
searchPerformed: true,
};
case types.GET_SEARCH_PROCESSED:
return {
...state,
processedDatas: action.payload,
searchPerformed: true,
};
case types.RESET_SEARCH:
return {
...initialState,

View File

@@ -24,12 +24,22 @@
font-family: "RobotoBold";
src: url("../../assets/fonts/roboto/Roboto-Bold.woff") format("woff");
}
@font-face {
font-family: "Tahoma";
src: url("../../assets/fonts/tahoma/tahoma.ttf") format("ttf");
}
@font-face {
font-family: "TahomaBold";
src: url("../../assets/fonts/tahoma/tahomabd.ttf") format("ttf");
}
@baseFont: "LGSmartUI";
@baseFontBold: "LGSmartUIBold";
@arialFont: "Arial";
@arialFontBold: "ArialBold";
@robotoFont: "Roboto";
@robotoFontBold: "RobotoBold";
@tahomaFont: "Tahoma";
@tahomaFontBold: "TahomaBold";
@globalHeight: 1080px;
@globalWidth: 1920px;
@@ -60,6 +70,7 @@
@COLOR_BLACK: #000000;
@COLOR_NAVY: #2c343f;
@COLOR_SKYBLUE: #e7ebef;
@COLOR_LIGHT_SKYBLUE: #edeff3;
/* ----- 추가 CommonStyles (chw) ----- */

View File

@@ -24,6 +24,16 @@ export default function SearchPanel() {
const searchDatas = useSelector(
(state) => state.search.searchDatas.data?.result.results
);
const {
theme: themeDatas,
show: showDatas,
item: itemDatas,
} = useSelector((state) => state.search.processedDatas.data);
const {
theme: themeCount,
show: showCount,
item: itemCount,
} = useSelector((state) => state.search.processedDatas.totalCount);
const searchPerformed = useSelector((state) => state.search.searchPerformed);
const [currentPage, setCurrentPage] = useState(1);
@@ -110,7 +120,15 @@ export default function SearchPanel() {
/>
{searchPerformed ? (
searchDatas && searchDatas.length > 0 ? (
<SearchResults contents={searchDatas} />
<SearchResults
themeDatas={themeDatas}
itemDatas={itemDatas}
showDatas={showDatas}
themeCount={themeCount}
showCount={showCount}
itemCount={itemCount}
query={searchQuery}
/>
) : (
<NoSearchResults />
)

View File

@@ -0,0 +1,35 @@
import React, { memo } from "react";
import Spottable from "@enact/spotlight/Spottable";
import css from "./SearchItemCard.module.less";
const ItemContainer = Spottable("div");
export default memo(function SearchItemCard({
dcPrice,
partnerLogo,
partnerName,
price,
reviewGrade,
soldout,
thumbnail,
title,
contentId,
...rest
}) {
return (
<ItemContainer className={css.card} {...rest}>
<div className={css.imageBox}>
<img src={thumbnail} alt={title} />
</div>
<div className={css.infoWrap}>
<p className={css.title}>{title}</p>
<div className={css.priceWrap}>
<span className={css.price}>{price}</span>
<strong className={css.dcPrice}>{dcPrice}</strong>
</div>
</div>
</ItemContainer>
);
});

View File

@@ -0,0 +1,58 @@
@import "../../../../../style/CommonStyle.module.less";
@import "../../../../../style/utils.module.less";
.card {
.size(@w: 561px, @h:150px);
background-color: @COLOR_LIGHT_SKYBLUE;
overflow: hidden;
border: 1px solid #ccc;
.flex();
.imageBox {
.size(@w: 150px, @h: 100%);
background-color: @COLOR_WHITE;
background-size: cover;
> img {
object-fit: cover;
width: 100%;
height: 100%;
}
}
.infoWrap {
.size(@w: calc(100% - 150px), @h: 100%);
padding-left: 24px;
.flex(@direction: column, @alignCenter: flex-start);
.title {
color: @COLOR_GRAY06;
.font(@fontFamily: @arialFont, @fontSize: 24px);
.elip(@clamp: 2);
line-height: 1.33;
letter-spacing: -0.6px;
margin-bottom: 20px;
}
.price {
color: @COLOR_GRAY06;
.font(@fontFamily: @arialFont, @fontSize: 20px);
line-height: 1.2;
letter-spacing: -0.5px;
text-decoration: line-through;
}
.dcPrice {
color: @PRIMARY_COLOR_RED;
.font(@fontFamily: @arialFontBold, @fontSize: 28px);
line-height: 0.86;
letter-spacing: -0.7px;
margin-left: 10px;
}
}
&:focus-within {
border: 4px solid @PRIMARY_COLOR_RED;
box-shadow: 0 0 50px 0 rgba(11, 8, 8, 0.5);
}
}

View File

@@ -0,0 +1,54 @@
import React, { useCallback } from "react";
import { VirtualGridList } from "@enact/sandstone/VirtualList";
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import ri from "@enact/ui/resolution";
import SearchItemCard from "./SearchItemCard/SearchItemCard";
import css from "./SearchItemResults.module.less";
const LIST_ITEM_CONF = {
ITEM_WIDTH: 561 * 2,
ITEM_HEIGHT: 150 * 2,
SPACING: 30 * 2,
};
const Container = SpotlightContainerDecorator(
{ leaveFor: { right: "" }, enterTo: "last-focused" },
"div"
);
export default function SearchItemResults({ itemDatas, itemCount, ...rest }) {
const renderItem = useCallback(
({ index, ...rest }) => {
const itemData = itemDatas[index];
return (
<SearchItemCard {...rest} key={itemDatas.contentId} {...itemData} />
);
},
[itemDatas]
);
return (
<Container className={css.container}>
{itemDatas && (
<VirtualGridList
className={css.grid}
dataSize={itemDatas.length}
direction="horizontal"
itemRenderer={renderItem}
itemSize={{
minWidth: ri.scale(LIST_ITEM_CONF.ITEM_WIDTH),
minHeight: ri.scale(LIST_ITEM_CONF.ITEM_HEIGHT),
}}
spacing={ri.scale(LIST_ITEM_CONF.SPACING)}
scrollMode="translate"
horizontalScrollbar="hidden"
noScrollByWheel
// onScrollStop={handleScrollStop}
/>
)}
</Container>
);
}

View File

@@ -0,0 +1,17 @@
@import "../../../../style/CommonStyle.module.less";
@import "../../../../style/utils.module.less";
.container {
.flex();
margin: 30px 0 50px;
width: 100%;
}
.grid {
height: 150px;
overflow: unset;
> div {
overflow: unset !important;
}
}

View File

@@ -1,5 +1,52 @@
import React from "react";
export default function SearchResults({ datas }) {
return <p>data</p>;
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import SectionTitle from "../../../components/SectionTitle/SectionTitle";
import { $L } from "../../../utils/helperMethods";
import SearchItemResults from "./SearchItemResults/SearchItemResults";
import css from "./SearchResults.module.less";
import SearchShowResults from "./SearchShowResults/SearchShowResults";
import SearchThemeResults from "./SearchThemeResults/SearchThemeResults";
const Container = SpotlightContainerDecorator(
{ enterTo: "last-focused" },
"div"
);
export default function SearchResults({
themeDatas,
itemDatas,
showDatas,
themeCount,
itemCount,
showCount,
query,
}) {
return (
<Container className={css.container}>
{themeDatas && (
<>
<SectionTitle title={$L("Hot Picks")} />
<SearchThemeResults themeDatas={themeDatas} themeCount={themeCount} />
</>
)}
{showDatas && (
<>
<SectionTitle title={$L("Shows") + " (" + showCount + ")"} />
<SearchShowResults showDatas={showDatas} showCount={showCount} />
</>
)}
{itemDatas && (
<>
<SectionTitle title={$L("Items") + " (" + itemCount + ")"} />
<SearchItemResults
itemDatas={itemDatas}
itemCount={itemCount}
query={query}
/>
</>
)}
</Container>
);
}

View File

@@ -1,19 +1,3 @@
@import "../../../style/CommonStyle.module.less";
@import "../../../style/utils.module.less";
.container {
background-color: yellowgreen;
&:focus-within {
border: 1px solid red;
}
}
.container2 {
height: 240px;
}
.itemList {
width: 100%;
height: 100%;
padding: 94px 60px 0 60px;
}

View File

@@ -0,0 +1,35 @@
import React, { memo } from "react";
import Spottable from "@enact/spotlight/Spottable";
import IcLiveShow from "../../../../../../assets/icon/tag/tag-liveshow.svg";
import css from "./SearchShowCard.module.less";
const CardContainer = Spottable("div");
export default memo(function SearchShowCard({
endTime,
liveFlag,
partnerId,
partnerLogo,
partnerName,
startTime,
thumbnail,
title,
contentId,
...rest
}) {
return (
<CardContainer className={css.card} {...rest}>
<div className={css.imageBox}>
{liveFlag === "Y" && (
<span className={css.liveBadge}>
<img src={IcLiveShow} alt="LIVE" />
</span>
)}
<img src={thumbnail} alt={title} />
</div>
<p className={css.title}>{title}</p>
</CardContainer>
);
});

View File

@@ -0,0 +1,52 @@
@import "../../../../../style/CommonStyle.module.less";
@import "../../../../../style/utils.module.less";
.card {
.size(@w: 344px, @h: 281px);
background-color: @COLOR_LIGHT_SKYBLUE;
border-radius: 10px;
overflow: hidden;
transform: scale(0.9);
transition: all 0.15s ease-in-out;
.imageBox {
.size(@w: 100%, @h: 182px);
background-color: @COLOR_WHITE;
background-size: cover;
position: relative;
.liveBadge {
position: absolute;
top: 10px;
right: 10px;
}
> img {
object-fit: cover;
width: 100%;
height: 100%;
}
}
.title {
width: 100%;
height: calc(100% - 182px);
color: @COLOR_GRAY06;
.font(@fontFamily: @arialFont, @fontSize: 20px);
.elip(@clamp:1);
line-height: 1.2;
letter-spacing: -0.5px;
display: flex;
align-items: center;
padding: 0 15px;
}
&:focus-within {
border: 4px solid @PRIMARY_COLOR_RED;
background-color: @COLOR_WHITE;
box-shadow: 0 0 50px 0 rgba(11, 8, 8, 0.5);
.elip(@clamp:2);
line-height: 1.5;
transform: scale(1);
}
}

View File

@@ -0,0 +1,53 @@
import React, { useCallback } from "react";
import VirtualGridList from "@enact/sandstone/VirtualList";
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import ri from "@enact/ui/resolution";
import SearchShowCard from "./SearchShowCard/SearchShowCard";
import css from "./SearchShowResults.module.less";
const LIST_ITEM_CONF = {
ITEM_WIDTH: 344 * 2,
ITEM_HEIGHT: 281 * 2,
SPACING: 0,
};
const Container = SpotlightContainerDecorator(
{ leaveFor: { right: "" }, enterTo: "last-focused" },
"div"
);
export default function SearchShowResults({ showDatas, showCount, ...rest }) {
const renderItem = useCallback(
({ index, ...rest }) => {
const showData = showDatas[index];
return (
<SearchShowCard {...rest} key={showData.contentId} {...showData} />
);
},
[showDatas]
);
return (
<Container className={css.container}>
{showDatas && (
<VirtualGridList
className={css.grid}
dataSize={showDatas.length}
direction="horizontal"
itemRenderer={renderItem}
itemSize={{
minWidth: ri.scale(LIST_ITEM_CONF.ITEM_WIDTH),
minHeight: ri.scale(LIST_ITEM_CONF.ITEM_HEIGHT),
}}
spacing={ri.scale(LIST_ITEM_CONF.SPACING)}
scrollMode="translate"
horizontalScrollbar="hidden"
noScrollByWheel
/>
)}
</Container>
);
}

View File

@@ -0,0 +1,17 @@
@import "../../../../style/CommonStyle.module.less";
@import "../../../../style/utils.module.less";
.container {
.flex();
margin: 30px 0 50px;
width: 100%;
}
.grid {
height: 281px;
overflow: unset;
> div {
overflow: unset !important;
}
}

View File

@@ -0,0 +1,32 @@
import React, { memo } from "react";
import Spottable from "@enact/spotlight/Spottable";
import css from "./SearchThemeCard.module.less";
const ThemeContainer = Spottable("div");
export default memo(function SearchThemeCard({
endTime,
keyword,
partnerLogo,
partnerName,
startTime,
title,
contentId,
...rest
}) {
return (
<ThemeContainer className={css.card} {...rest}>
<h2 className={css.title}>{title}</h2>
<ul className={css.keywordWrap}>
{keyword &&
keyword.map((item, index) => (
<li key={index} className={css.keywordList}>
<span className={css.keyword}># {item}</span>
</li>
))}
</ul>
</ThemeContainer>
);
});

View File

@@ -0,0 +1,51 @@
@import "../../../../../style/CommonStyle.module.less";
@import "../../../../../style/utils.module.less";
.card {
.size(@w: 560px, @h: 140px);
background-image: url("../../../../../../assets/searchpanel/bg_srch_curation.png");
border: 2px solid #ccc;
overflow: hidden;
background-size: cover;
background-repeat: no-repeat;
background-position: center top;
padding: 25px 20px;
.title {
color: @COLOR_GRAY06;
.font(@fontFamily: @tahomaFontBold, @fontSize: 26px);
line-height: 1.31;
letter-spacing: -0.65px;
color: @COLOR_GRAY06;
font-weight: 900;
.elip(@clamp: 2);
}
.keywordWrap {
margin-top: 1px;
display: flex;
color: #c06;
.font(@fontFamily: @arialFontBold, @fontSize: 24px);
letter-spacing: -0.6px;
line-height: 1.67;
min-height: 26px;
.keywordList {
margin-right: 14px;
}
}
&:focus-within {
background-position: center bottom;
border: 4px solid @PRIMARY_COLOR_RED;
box-shadow: 0 0 50px 0 rgba(11, 8, 8, 0.5);
.title {
color: @COLOR_WHITE;
}
.keywordWrap {
color: @COLOR_WHITE;
}
}
}

View File

@@ -0,0 +1,57 @@
import React, { useCallback } from "react";
import { VirtualGridList } from "@enact/sandstone/VirtualList";
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import ri from "@enact/ui/resolution";
import SearchThemeCard from "./SearchThemeCard/SearchThemeCard";
import css from "./SearchThemeResults.module.less";
const LIST_ITEM_CONF = {
ITEM_WIDTH: 560 * 2,
ITEM_HEIGHT: 140 * 2,
SPACING: 30 * 2,
};
const Container = SpotlightContainerDecorator(
{ leaveFor: { right: "" }, enterTo: "last-focused" },
"div"
);
export default function SearchThemeResults({
themeDatas,
themeCount,
...rest
}) {
const renderItem = useCallback(
({ index, ...rest }) => {
const themeData = themeDatas[index];
return (
<SearchThemeCard {...rest} key={themeData.contentId} {...themeData} />
);
},
[themeDatas]
);
return (
<Container className={css.container}>
{themeDatas && (
<VirtualGridList
className={css.grid}
dataSize={themeDatas.length}
direction="horizontal"
itemRenderer={renderItem}
itemSize={{
minWidth: ri.scale(LIST_ITEM_CONF.ITEM_WIDTH),
minHeight: ri.scale(LIST_ITEM_CONF.ITEM_HEIGHT),
}}
spacing={ri.scale(LIST_ITEM_CONF.SPACING)}
scrollMode="translate"
horizontalScrollbar="hidden"
noScrollByWheel
/>
)}
</Container>
);
}

View File

@@ -0,0 +1,17 @@
@import "../../../../style/CommonStyle.module.less";
@import "../../../../style/utils.module.less";
.container {
.flex();
margin: 30px 0 50px;
width: 100%;
}
.grid {
height: 141px;
overflow: unset;
> div {
overflow: unset !important;
}
}