---
title: "垂直recommendationウィジェットテンプレート"
slug: "the-vertical-recommendation-widget-template"
abstract: "Component Search UI Componentsにおける垂直recommendationウィジェットテンプレートのバリエーションおよびAPI参照。CLIコマンドとコードブロックを含みます。"
---
# 垂直recommendationウィジェットテンプレート

縦方向のrecommendationウィジェットはページ付きコントロール付きのrecommendationsを表示します。

![Screenshot of a vertical recommendation widget.](https://doc.sitecore.com/api/markdown-image/search/ja/developers/search-js-sdk-for-react/images/recommendation-vert.png)

以下のセクションでは、垂直recommendationウィジェットテンプレートについて説明し、API参照と、Reactプロジェクトの[WidgetsProvider](https://doc.sitecore.com/search/ja/developers/search-js-sdk-for-react/js-sdk-reference/providers/widgetsprovider.html "WidgetsProvider")にJS SDK for Reactを使ってコードブロックを作成する方法Search手順が含まれています。ウィジェットは [検索結果クエリフ](https://doc.sitecore.com/search/ja/developers/search-js-sdk-for-react/js-sdk-reference/query-hooks/usesearchresults.html "useSearchResults") ックを使ってデータを要求・取得します。

ウィジェットコンポーネントを作成するには、Search [UIコンポーネントCLI](https://www.npmjs.com/package/@sitecore-search/cli)やコマンドラインインターフェース、またはコードブロックを手動でコピー&ペーストする方法があります。

## コンポーネントを生成または作成してください

このコンポーネントは、プレーンまたはスタイリングなし、スタイル付き、CSSスタイル、CSSモジュールスタイルの各バリアントで利用可能です。以下のセクションは、TypescriptおよびJavaScriptでコンポーネントを生成または手動で作成するための手順です。

> !重要ウィジェットテンプレート内のエンティティモデルを実装に合わせて調整することを忘れないでください。

### プレーン成分

シンプルなウィジェットテンプレートはスタイリングされておらず、CSSスタイルやテーマでカスタマイズ可能です。コンポーネントのデザインや内容を完全にコントロールできます。

#### TS CLI { .notintoc }

Sitecore Search CLIを使えば、ウィジェットテンプレートの必要なファイルをReactプロジェクトに複製できます。以下の手順には、プロジェクトにCLIをインストールする手順と、ウィジェットコンポーネントを作成する場所を指定する手順が含まれています。

もし以前にCLIをプロジェクトにインストールしていて、.sc-search-settings.jsonというファイルに場所が定義されていれば、直接ステップ3に進めます。

プロジェクトにUI Components CLIをインストールするには:

1.  Search UI Components CLIをインストールするには、IDEまたはコマンドプロンプトのターミナルを開き、以下のコマンドを実行してください。
    
    npx install --save-dev @sitecore-search/cli
    
2.  ウィジェットコンポーネントが作成される場所を定義するには、プロジェクトのルートにあるファイル .sc-search-settings.jsonで、以下のコードブロックで示すように指定してください。
    
    {
     "components-path": "src-test/components"
    }
    
3.  ウィジェットコンポーネントを作成するには、.sc-search-settings.jsonファイルで定義した場所に移動し、IDEまたはコマンドプロンプトのターミナルを開き、以下のコマンドを実行します。
    
    npx sc-search new-widget --language typescript --template recommendation-basic-vertical --entity content --styling plain
    

#### TSマニュアル { .notintoc }

Reactプロジェクトで手動でウィジェットテンプレートを作成する際は、必要な依存関係をインストールし、必要なファイルもすべて作成する必要があります。

必要なNPMパッケージを事前にインストールしていれば、依存関係のインストール手順を省略できます。

プロジェクト内でこのウィジェットテンプレートを使ってウィジェットコンポーネントを作成するには:

1.  プロジェクト内で開発依存関係をインストールするには、IDEのターミナルまたはコマンドプロンプトを開き、以下のコマンドを実行します。
    
    npm install @radix-ui/react-icons@^1.3.0
    
2.  ウィジェットコンポーネント用のフォルダに「 index.tsx 」というファイルを作成し、そのファイル内に次のコードブロックを貼り付けます。
    
    // index.tsx
    import type { RecommendationInitialState } from '@sitecore-search/react';
    import { WidgetDataType, useRecommendation, widget } from '@sitecore-search/react';
    import { ArticleCard, Presence } from '@sitecore-search/ui';
    
    type ArticleModel = {
     id: string;
     name: string;
     author?: string;
     url?: string;
     image\_url?: string;
     source\_id?: string;
    };
    type RecommendationProps = {
     title?: string;
     itemsToDisplay?: number;
    };
    type InitialState = RecommendationInitialState<'itemsPerPage'>;
    const DEFAULT\_IMG\_URL = 'https://placehold.co/500x300?text=No%20Image'; // TODO: Update with corresponding fallback image
    
    export const RecommendationBasicVerticalComponent = ({ title = '', itemsToDisplay = 6 }: RecommendationProps) => {
     const {
     widgetRef,
     actions: { onItemClick },
     queryResult: { isLoading, isFetching, data: { content: articles =  } = {} },
     } = useRecommendation<ArticleModel, InitialState>({
     state: {
     itemsPerPage: itemsToDisplay,
     },
     });
     const loading = isLoading || isFetching;
     return (
     <div>
     <Presence present={loading}>
     <div>
     <svg aria-busy={loading} aria-hidden={!loading} focusable="false" role="progressbar" viewBox="0 0 20 20">
     <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
     </svg>
     </div>
     </Presence>
     {!loading && (
     <>
     {title && <h3>{title}</h3>}
     <div ref={widgetRef}>
     {articles.map((item, index) => (
     <ArticleCard.Root key={item.id}>
     <div>
     <ArticleCard.Image src={item?.image\_url || DEFAULT\_IMG\_URL} />
     </div>
     <div>
     <a
     href=https://doc.sitecore.com/search/ja/developers/search-js-sdk-for-react/ui-components-reference/templates-for-content-items/recommendation/%7Bitem.url%7D
     onClick={(event) => {
     event.preventDefault();
     onItemClick({
     id: item.id,
     index,
     sourceId: item.source\_id,
     });
     }}
     >
     <ArticleCard.Title>{item.name}</ArticleCard.Title>
     <ArticleCard.Subtitle>{item.author}</ArticleCard.Subtitle>
     </a>
     </div>
     </ArticleCard.Root>
     ))}
     </div>
     </>
     )}
     </div>
     );
    };
    const RecommendationBasicVerticalWidget = widget(RecommendationBasicVerticalComponent, WidgetDataType.RECOMMENDATION, 'content');
    export default RecommendationBasicVerticalWidget;
    

#### JS CLI { .notintoc }

Sitecore Search CLIを使えば、ウィジェットテンプレートの必要なファイルをReactプロジェクトに複製できます。以下の手順には、プロジェクトにCLIをインストールする手順と、ウィジェットコンポーネントを作成する場所を指定する手順が含まれています。

もし以前にCLIをプロジェクトにインストールしていて、.sc-search-settings.jsonというファイルに場所が定義されていれば、直接ステップ3に進めます。

プロジェクトにUI Components CLIをインストールするには:

1.  Search UI Components CLIをインストールするには、IDEまたはコマンドプロンプトのターミナルを開き、以下のコマンドを実行してください。
    
    npx install --save-dev @sitecore-search/cli
    
2.  ウィジェットコンポーネントが作成される場所を定義するには、プロジェクトのルートにあるファイル .sc-search-settings.jsonで、以下のコードブロックで示すように指定してください。
    
    {
     "components-path": "src-test/components"
    }
    
3.  .sc-search-settings.jsonに記載された場所で、index.jsxファイル内のウィジェットコンポーネントを作成するには、IDEのターミナルに以下のコマンドを貼り付けてください。
    
    npx sc-search new-widget --language javascript --template recommendation-basic-vertical --entity content --styling plain
    

#### JS manual { .notintoc }

Reactプロジェクトで手動でウィジェットテンプレートを作成する際は、必要な依存関係をインストールし、必要なファイルもすべて作成する必要があります。

必要なNPMパッケージを事前にインストールしていれば、依存関係のインストール手順を省略できます。

プロジェクト内でこのウィジェットテンプレートを使ってウィジェットコンポーネントを作成するには:

1.  プロジェクト内で開発依存関係をインストールするには、IDEのターミナルまたはコマンドプロンプトを開き、以下のコマンドを実行します。
    
    npm install @radix-ui/react-icons@^1.3.0
    
2.  ウィジェットコンポーネント用のフォルダに「 index.jsx 」というファイルを作成し、そのファイル内に次のコードブロックを貼り付けます。
    
    // index.jsx
    import { WidgetDataType, useRecommendation, widget } from '@sitecore-search/react';
    import { ArticleCard, Presence } from '@sitecore-search/ui';
    
    const DEFAULT\_IMG\_URL = 'https://placehold.co/500x300?text=No%20Image'; // TODO: Update with corresponding fallback image
    
    export const RecommendationBasicVerticalComponent = ({ title = '', itemsToDisplay = 6 }) => {
     const {
     widgetRef,
     actions: { onItemClick },
     queryResult: { isLoading, isFetching, data: { content: articles =  } = {} },
     } = useRecommendation({
     state: {
     itemsPerPage: itemsToDisplay,
     },
     });
     const loading = isLoading || isFetching;
     return (
     <div>
     <Presence present={loading}>
     <div>
     <svg aria-busy={loading} aria-hidden={!loading} focusable="false" role="progressbar" viewBox="0 0 20 20">
     <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
     </svg>
     </div>
     </Presence>
     {!loading && (
     <>
     {title && <h3>{title}</h3>}
     <div ref={widgetRef}>
     {articles.map((item, index) => (
     <ArticleCard.Root key={item.id}>
     <div>
     <ArticleCard.Image src={item?.image\_url || DEFAULT\_IMG\_URL} />
     </div>
     <div>
     <a
     href=https://doc.sitecore.com/search/ja/developers/search-js-sdk-for-react/ui-components-reference/templates-for-content-items/recommendation/%7Bitem.url%7D
     onClick={(event) => {
     event.preventDefault();
     onItemClick({
     id: item.id,
     index,
     sourceId: item.source\_id,
     });
     }}
     >
     <ArticleCard.Title>{item.name}</ArticleCard.Title>
     <ArticleCard.Subtitle>{item.author}</ArticleCard.Subtitle>
     </a>
     </div>
     </ArticleCard.Root>
     ))}
     </div>
     </>
     )}
     </div>
     );
    };
    const RecommendationBasicVerticalWidget = widget(RecommendationBasicVerticalComponent, WidgetDataType.RECOMMENDATION, 'content');
    export default RecommendationBasicVerticalWidget;
    

### スタイル付きコンポーネント

スタイル付きウィジェットテンプレートは、スタイル付きUIプリミティブで構築されます。カスタマイズするには、スタイル付きコンポーネントを編集する必要があります。 styledファイル内のコンポーネントのデザインや内容をコントロールできます。

#### TS CLI { .notintoc }

Sitecore Search CLIを使えば、ウィジェットテンプレートの必要なファイルをReactプロジェクトに複製できます。以下の手順には、プロジェクトにCLIをインストールする手順と、ウィジェットコンポーネントを作成する場所を指定する手順が含まれています。

もし以前にCLIをプロジェクトにインストールしていて、.sc-search-settings.jsonというファイルに場所が定義されていれば、直接ステップ3に進めます。

プロジェクトにUI Components CLIをインストールするには:

1.  Search UI Components CLIをインストールするには、IDEまたはコマンドプロンプトのターミナルを開き、以下のコマンドを実行してください。
    
    npx install --save-dev @sitecore-search/cli
    
2.  ウィジェットコンポーネントが作成される場所を定義するには、プロジェクトのルートにあるファイル .sc-search-settings.jsonで、以下のコードブロックで示すように指定してください。
    
    {
     "components-path": "src-test/components"
    }
    
3.  index.tsxおよびstyled.tsファイルでウィジェットコンポーネントを作成するには、.sc-search-settings.jsonファイルで定義した場所に移動し、IDEまたはコマンドプロンプトのターミナルを開き、以下のコマンドを実行します。
    
    npx sc-search new-widget --language typescript --template recommendation-basic-vertical --entity content --styling styled
    

#### TSマニュアル { .notintoc }

Reactプロジェクトで手動でウィジェットテンプレートを作成する際は、必要な依存関係をインストールし、必要なファイルもすべて作成する必要があります。

必要なNPMパッケージを事前にインストールしていれば、依存関係のインストール手順を省略できます。

プロジェクト内でこのウィジェットテンプレートを使ってウィジェットコンポーネントを作成するには:

1.  プロジェクト内で開発依存関係をインストールするには、IDEのターミナルまたはコマンドプロンプトを開き、以下のコマンドを実行します。
    
    npm install styled-components@^6.1.0 @radix-ui/react-icons@^1.3.0
    
2.  プロジェクト内で開発依存関係をインストールするには、IDEのターミナルまたはコマンドプロンプトを開き、以下のコマンドを実行します。
    
    npm install --save-dev @types/styled-components@^5.1.29
    
3.  ウィジェットコンポーネント用のフォルダ内に「 index.tsx 」と「 styled.ts 」というファイルを作成し、それぞれのファイルにコードブロックを貼り付けます。
    
    // index.tsx
    import type { RecommendationInitialState } from '@sitecore-search/react';
    import { WidgetDataType, useRecommendation, widget } from '@sitecore-search/react';
    import { Presence } from '@sitecore-search/ui';
    
    import {
     ArticleCardStyled,
     GridStyled,
     LoaderAnimation,
     LoaderContainer,
     RecommendationContainer,
     TitleStyled,
    } from './styled';
    
    type ArticleModel = {
     id: string;
     name: string;
     author?: string;
     url?: string;
     image\_url?: string;
     source\_id?: string;
    };
    
    type RecommendationProps = {
     title?: string;
     itemsToDisplay?: number;
    };
    
    type InitialState = RecommendationInitialState<'itemsPerPage'>;
    
    const DEFAULT\_IMG\_URL = 'https://placehold.co/500x300?text=No%20Image'; // TODO: Update with corresponding fallback image
    
    export const RecommendationBasicVerticalComponent = ({ title = '', itemsToDisplay = 6 }: RecommendationProps) => {
     const {
     widgetRef,
     actions: { onItemClick },
     queryResult: { isLoading, isFetching, data: { content: articles =  } = {} },
     } = useRecommendation<ArticleModel, InitialState>({
     state: {
     itemsPerPage: itemsToDisplay,
     },
     });
    
     const loading = isLoading || isFetching;
    
     return (
     <RecommendationContainer>
     <Presence present={loading}>
     <LoaderContainer>
     <LoaderAnimation
     aria-busy={loading}
     aria-hidden={!loading}
     focusable="false"
     role="progressbar"
     viewBox="0 0 20 20"
     >
     <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
     </LoaderAnimation>
     </LoaderContainer>
     </Presence>
     {!loading && (
     <>
     {title && <TitleStyled>{title}</TitleStyled>}
     <GridStyled ref={widgetRef}>
     {articles.map((item, index) => (
     <ArticleCardStyled.Root key={item.id}>
     <ArticleCardStyled.ImageWrapper>
     <ArticleCardStyled.Image src={item?.image\_url || DEFAULT\_IMG\_URL} />
     </ArticleCardStyled.ImageWrapper>
     <ArticleCardStyled.Content>
     <ArticleCardStyled.Link
     href={item.url}
     onClick={(event) => {
     event.preventDefault();
     onItemClick({ id: item.id, index, sourceId: item.source\_id });
     }}
     >
     <ArticleCardStyled.Title>{item.name}</ArticleCardStyled.Title>
     <ArticleCardStyled.Subtitle>{item.author}</ArticleCardStyled.Subtitle>
     </ArticleCardStyled.Link>
     </ArticleCardStyled.Content>
     </ArticleCardStyled.Root>
     ))}
     </GridStyled>
     </>
     )}
     </RecommendationContainer>
     );
    };
    
    const RecommendationBasicVerticalWidget = widget(RecommendationBasicVerticalComponent, WidgetDataType.RECOMMENDATION, 'content');
    export default RecommendationBasicVerticalWidget;
    
    // styled.ts
    import { keyframes, styled } from 'styled-components';
    
    import { ArticleCard, theme } from '@sitecore-search/ui';
    
    const ArticleCardRootStyled = styled(ArticleCard.Root)\`
     cursor: pointer;
     box-sizing: border-box;
     box-shadow: 0 0 2px 2px ${theme.vars.palette.grey'400'};
     background: #fff;
     display: flex;
     flex-wrap: nowrap;
     margin: ${theme.vars.spacing.m};
     max-height: 200px;
     padding: ${theme.vars.spacing.m};
     position: relative;
     -webkit-transition: 0.3s all ease;
     transition: 0.15s all ease;
     width: 100%;
    
     &:focus-within {
     box-shadow: 0 0 8px 3px rgba(0, 0, 0, 0.15);
     transform: scale(1.025);
     opacity: 1;
     transition: 0.35s all ease;
     }
    
     &:hover {
     box-shadow: 0 0 8px 3px rgba(0, 0, 0, 0.15);
     transform: scale(1.025);
     opacity: 1;
     transition: 0.35s all ease;
     }
    \`;
    
    const ArticleCardContentStyled = styled.div\`
     flex-grow: 2;
     padding-left: ${theme.vars.spacing.m};
    \`;
    
    const ArticleCardLinkStyled = styled.a\`
     color: ${theme.vars.palette.text.primary};
     font-size: ${theme.vars.typography.fontSize3.fontSize};
     text-decoration: none;
    
     &:hover {
     text-decoration: none;
     }
    
     &:focus {
     text-decoration: none;
     }
     &::after {
     position: absolute;
     inset: 0;
     display: block;
     content: ' ';
     }
    \`;
    
    const ArticleCardImageStyled = styled(ArticleCard.Image)\`
     width: 100%;
    \`;
    
    const ArticleCardImageWrapperStyled = styled.div\`
     position: relative;
    
     flex: 0 0 25%;
     max-width: 25%;
     height: 100%;
    \`;
    const ArticleCardTitleStyled = styled(ArticleCard.Title)\`
     color: ${theme.vars.palette.text.primary};
     font-family: ${theme.vars.typography.fontFamilySystem};
     font-size: ${theme.vars.typography.h3.fontSize};
     font-weight: ${theme.vars.typography.h3.fontWeight};
     line-height: ${theme.vars.typography.h3.lineHeight};
     margin: 0 0 ${theme.vars.spacing.m};
    \`;
    
    const ArticleCardSubtitleStyled = styled(ArticleCard.Subtitle)\`
     color: ${theme.vars.palette.text.secondary};
     font-family: ${theme.vars.typography.fontFamilySystem};
     font-size: ${theme.vars.typography.fontSize3.fontSize};
     font-weight: ${theme.vars.typography.fontSize3.fontWeight};
     line-height: ${theme.vars.typography.fontSize3.lineHeight};
     margin: 0 0 ${theme.vars.spacing.m};
    \`;
    
    export const TitleStyled = styled.h3\`
     color: ${theme.vars.palette.primary.main};
     font-family: ${theme.vars.typography.fontFamilySystem};
     font-size: ${theme.vars.typography.h3.fontSize};
    \`;
    
    export const GridStyled = styled.div\`
     display: flex;
     flex-flow: row wrap;
    \`;
    
    export const LoaderContainer = styled.div\`
     align-items: center;
     display: flex;
     min-height: 50vh;
    \`;
    
    const Rotate = keyframes\`
     from {
     transform: rotate(0deg);
     }
     to {
     transform: rotate(360deg);
     }
    \`;
    
    export const RecommendationContainer = styled.div\`
     max-width: 1280px;
     margin: 0 auto;
    \`;
    
    export const LoaderAnimation = styled.svg\`
     animation: ${Rotate} 2s linear infinite;
     display: block;
     fill: ${theme.vars.palette.primary.main};
     height: 50px;
     margin: auto;
     width: 50px;
    \`;
    
    export const ArticleCardStyled = {
     Root: ArticleCardRootStyled,
     Content: ArticleCardContentStyled,
     Link: ArticleCardLinkStyled,
     Image: ArticleCardImageStyled,
     ImageWrapper: ArticleCardImageWrapperStyled,
     Title: ArticleCardTitleStyled,
     Subtitle: ArticleCardSubtitleStyled,
    };
    

#### JS CLI { .notintoc }

Sitecore Search CLIを使えば、ウィジェットテンプレートの必要なファイルをReactプロジェクトに複製できます。以下の手順には、プロジェクトにCLIをインストールする手順と、ウィジェットコンポーネントを作成する場所を指定する手順が含まれています。

もし以前にCLIをプロジェクトにインストールしていて、.sc-search-settings.jsonというファイルに場所が定義されていれば、直接ステップ3に進めます。

プロジェクトにUI Components CLIをインストールするには:

1.  Search UI Components CLIをインストールするには、IDEまたはコマンドプロンプトのターミナルを開き、以下のコマンドを実行してください。
    
    npx install --save-dev @sitecore-search/cli
    
2.  ウィジェットコンポーネントが作成される場所を定義するには、プロジェクトのルートにあるファイル .sc-search-settings.jsonで、以下のコードブロックで示すように指定してください。
    
    {
     "components-path": "src-test/components"
    }
    
3.  index.jsxおよびstyled.jsファイルでウィジェットコンポーネントを作成するには、.sc-search-settings.jsonファイルで定義した場所に移動し、IDEまたはコマンドプロンプトのターミナルを開き、以下のコマンドを実行します。
    
    npx sc-search new-widget --language javascript --template recommendation-basic-vertical --entity content --styling styled
    

#### JS manual { .notintoc }

Reactプロジェクトで手動でウィジェットテンプレートを作成する際は、必要な依存関係をインストールし、必要なファイルもすべて作成する必要があります。

必要なNPMパッケージを事前にインストールしていれば、依存関係のインストール手順を省略できます。

プロジェクト内でこのウィジェットテンプレートを使ってウィジェットコンポーネントを作成するには:

1.  プロジェクト内で開発依存関係をインストールするには、IDEのターミナルまたはコマンドプロンプトを開き、以下のコマンドを実行します。
    
    npm install styled-components@^6.1.0 @radix-ui/react-icons@^1.3.0
    
2.  プロジェクト内で開発依存関係をインストールするには、IDEのターミナルまたはコマンドプロンプトを開き、以下のコマンドを実行します。
    
    npm install --save-dev @types/styled-components@^5.1.29
    
3.  ウィジェットコンポーネント用のフォルダ内に「 index.jsx 」と「 styled.js 」というファイルを作成し、それぞれのファイルにコードブロックを貼り付けます。
    
    // index.jsx
    import { WidgetDataType, useRecommendation, widget } from '@sitecore-search/react';
    import { Presence } from '@sitecore-search/ui';
    
    import {
     ArticleCardStyled,
     GridStyled,
     LoaderAnimation,
     LoaderContainer,
     RecommendationContainer,
     TitleStyled,
    } from './styled';
    
    const DEFAULT\_IMG\_URL = 'https://placehold.co/500x300?text=No%20Image'; // TODO: Update with corresponding fallback image
    
    export const RecommendationBasicVerticalComponent = ({ title = '', itemsToDisplay = 6 }) => {
     const {
     widgetRef,
     actions: { onItemClick },
     queryResult: { isLoading, isFetching, data: { content: articles =  } = {} },
     } = useRecommendation({
     state: {
     itemsPerPage: itemsToDisplay,
     },
     });
     const loading = isLoading || isFetching;
     return (
     <RecommendationContainer>
     <Presence present={loading}>
     <LoaderContainer>
     <LoaderAnimation
     aria-busy={loading}
     aria-hidden={!loading}
     focusable="false"
     role="progressbar"
     viewBox="0 0 20 20"
     >
     <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
     </LoaderAnimation>
     </LoaderContainer>
     </Presence>
     {!loading && (
     <>
     {title && <TitleStyled>{title}</TitleStyled>}
     <GridStyled ref={widgetRef}>
     {articles.map((item, index) => (
     <ArticleCardStyled.Root key={item.id}>
     <ArticleCardStyled.ImageWrapper>
     <ArticleCardStyled.Image src={item?.image\_url || DEFAULT\_IMG\_URL} />
     </ArticleCardStyled.ImageWrapper>
     <ArticleCardStyled.Content>
     <ArticleCardStyled.Link
     href={item.url}
     onClick={(event) => {
     event.preventDefault();
     onItemClick({
     id: item.id,
     index,
     sourceId: item.source\_id,
     });
     }}
     >
     <ArticleCardStyled.Title>{item.name}</ArticleCardStyled.Title>
     <ArticleCardStyled.Subtitle>{item.author}</ArticleCardStyled.Subtitle>
     </ArticleCardStyled.Link>
     </ArticleCardStyled.Content>
     </ArticleCardStyled.Root>
     ))}
     </GridStyled>
     </>
     )}
     </RecommendationContainer>
     );
    };
    const RecommendationBasicVerticalWidget = widget(RecommendationBasicVerticalComponent, WidgetDataType.RECOMMENDATION, 'content');
    export default RecommendationBasicVerticalWidget;
    
    // styled.js
    import { keyframes, styled } from 'styled-components';
    
    import { ArticleCard, theme } from '@sitecore-search/ui';
    
    const ArticleCardRootStyled = styled(ArticleCard.Root)\`
     cursor: pointer;
     box-sizing: border-box;
     box-shadow: 0 0 2px 2px ${theme.vars.palette.grey'400'};
     background: #fff;
     display: flex;
     flex-wrap: nowrap;
     margin: ${theme.vars.spacing.m};
     max-height: 200px;
     padding: ${theme.vars.spacing.m};
     position: relative;
     -webkit-transition: 0.3s all ease;
     transition: 0.15s all ease;
     width: 100%;
    
     &:focus-within {
     box-shadow: 0 0 8px 3px rgba(0, 0, 0, 0.15);
     transform: scale(1.025);
     opacity: 1;
     transition: 0.35s all ease;
     }
    
     &:hover {
     box-shadow: 0 0 8px 3px rgba(0, 0, 0, 0.15);
     transform: scale(1.025);
     opacity: 1;
     transition: 0.35s all ease;
     }
    \`;
    const ArticleCardContentStyled = styled.div\`
     flex-grow: 2;
     padding-left: ${theme.vars.spacing.m};
    \`;
    const ArticleCardLinkStyled = styled.a\`
     color: ${theme.vars.palette.text.primary};
     font-size: ${theme.vars.typography.fontSize3.fontSize};
     text-decoration: none;
    
     &:hover {
     text-decoration: none;
     }
    
     &:focus {
     text-decoration: none;
     }
     &::after {
     position: absolute;
     inset: 0;
     display: block;
     content: ' ';
     }
    \`;
    const ArticleCardImageStyled = styled(ArticleCard.Image)\`
     width: 100%;
    \`;
    const ArticleCardImageWrapperStyled = styled.div\`
     position: relative;
    
     flex: 0 0 25%;
     max-width: 25%;
     height: 100%;
    \`;
    const ArticleCardTitleStyled = styled(ArticleCard.Title)\`
     color: ${theme.vars.palette.text.primary};
     font-family: ${theme.vars.typography.fontFamilySystem};
     font-size: ${theme.vars.typography.h3.fontSize};
     font-weight: ${theme.vars.typography.h3.fontWeight};
     line-height: ${theme.vars.typography.h3.lineHeight};
     margin: 0 0 ${theme.vars.spacing.m};
    \`;
    const ArticleCardSubtitleStyled = styled(ArticleCard.Subtitle)\`
     color: ${theme.vars.palette.text.secondary};
     font-family: ${theme.vars.typography.fontFamilySystem};
     font-size: ${theme.vars.typography.fontSize3.fontSize};
     font-weight: ${theme.vars.typography.fontSize3.fontWeight};
     line-height: ${theme.vars.typography.fontSize3.lineHeight};
     margin: 0 0 ${theme.vars.spacing.m};
    \`;
    export const TitleStyled = styled.h3\`
     color: ${theme.vars.palette.primary.main};
     font-family: ${theme.vars.typography.fontFamilySystem};
     font-size: ${theme.vars.typography.h3.fontSize};
    \`;
    export const GridStyled = styled.div\`
     display: flex;
     flex-flow: row wrap;
    \`;
    export const LoaderContainer = styled.div\`
     align-items: center;
     display: flex;
     min-height: 50vh;
    \`;
    const Rotate = keyframes\`
     from {
     transform: rotate(0deg);
     }
     to {
     transform: rotate(360deg);
     }
    \`;
    export const RecommendationContainer = styled.div\`
     max-width: 1280px;
     margin: 0 auto;
    \`;
    export const LoaderAnimation = styled.svg\`
     animation: ${Rotate} 2s linear infinite;
     display: block;
     fill: ${theme.vars.palette.primary.main};
     height: 50px;
     margin: auto;
     width: 50px;
    \`;
    export const ArticleCardStyled = {
     Root: ArticleCardRootStyled,
     Content: ArticleCardContentStyled,
     Link: ArticleCardLinkStyled,
     Image: ArticleCardImageStyled,
     ImageWrapper: ArticleCardImageWrapperStyled,
     Title: ArticleCardTitleStyled,
     Subtitle: ArticleCardSubtitleStyled,
    };
    

### CSSスタイルのコンポーネント

CSSスタイル付きウィジェットテンプレートはUIプリミティブを使用し、それらはstyles.cssでスタイリングされます。カスタマイズするには、コンポーネントとCSSを編集できます。

#### TS CLI { .notintoc }

Sitecore Search CLIを使えば、ウィジェットテンプレートの必要なファイルをReactプロジェクトに複製できます。以下の手順には、プロジェクトにCLIをインストールする手順と、ウィジェットコンポーネントを作成する場所を指定する手順が含まれています。

もし以前にCLIをプロジェクトにインストールしていて、.sc-search-settings.jsonというファイルに場所が定義されていれば、直接ステップ3に進めます。

プロジェクトにUI Components CLIをインストールするには:

1.  Search UI Components CLIをインストールするには、IDEまたはコマンドプロンプトのターミナルを開き、以下のコマンドを実行してください。
    
    npx install --save-dev @sitecore-search/cli
    
2.  ウィジェットコンポーネントが作成される場所を定義するには、プロジェクトのルートにあるファイル .sc-search-settings.jsonで、以下のコードブロックで示すように指定してください。
    
    {
     "components-path": "src-test/components"
    }
    
3.  index.tsxおよびstyles.cssファイルでウィジェットコンポーネントを作成するには、.sc-search-settings.jsonファイルで定義した場所に移動し、IDEまたはコマンドプロンプトのターミナルを開き、以下のコマンドを実行します。
    
    npx sc-search new-widget --language typescript --template recommendation-basic-vertical --entity content --styling css
    

#### TSマニュアル { .notintoc }

Reactプロジェクトで手動でウィジェットテンプレートを作成する際は、必要な依存関係をインストールし、必要なファイルもすべて作成する必要があります。

プロジェクト内でこのウィジェットテンプレートを使ってウィジェットコンポーネントを作成するには:

1.  プロジェクト内で開発依存関係をインストールするには、IDEのターミナルまたはコマンドプロンプトを開き、以下のコマンドを実行します。
    
    npm install @radix-ui/react-icons@^1.3.0
    
2.  ウィジェットコンポーネント用のフォルダ内に「 index.tsx 」と「 styles.css 」というファイルを作成し、それぞれのファイルにコードブロックを貼り付けます。
    
    // index.tsx
    import type { RecommendationInitialState } from '@sitecore-search/react';
    import { WidgetDataType, useRecommendation, widget } from '@sitecore-search/react';
    import { ArticleCard, Presence } from '@sitecore-search/ui';
    
    import './styles.css';
    
    type ArticleModel = {
     id: string;
     name: string;
     author?: string;
     url?: string;
     image\_url?: string;
     source\_id?: string;
    };
    type RecommendationProps = {
     title?: string;
     itemsToDisplay?: number;
    };
    type InitialState = RecommendationInitialState<'itemsPerPage'>;
    const DEFAULT\_IMG\_URL = 'https://placehold.co/500x300?text=No%20Image'; // TODO: Update with corresponding fallback image
    
    export const RecommendationBasicVerticalComponent = ({ title = '', itemsToDisplay = 6 }: RecommendationProps) => {
     const {
     widgetRef,
     actions: { onItemClick },
     queryResult: { isLoading, isFetching, data: { content: articles =  } = {} },
     } = useRecommendation<ArticleModel, InitialState>({
     state: {
     itemsPerPage: itemsToDisplay,
     },
     });
     const loading = isLoading || isFetching;
     return (
     <div className="sitecore-recommendation-container">
     <Presence present={loading}>
     <div className="sitecore-loader-container">
     <svg
     aria-busy={loading}
     aria-hidden={!loading}
     focusable="false"
     role="progressbar"
     viewBox="0 0 20 20"
     className="sitecore-loader-animation"
     >
     <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
     </svg>
     </div>
     </Presence>
     {!loading && (
     <>
     {title && <h3 className="sitecore-title">{title}</h3>}
     <div ref={widgetRef} className="sitecore-grid">
     {articles.map((item, index) => (
     <ArticleCard.Root key={item.id} className="sitecore-article-card-root">
     <div className="sitecore-article-card-image-wrapper">
     <ArticleCard.Image src={item?.image\_url || DEFAULT\_IMG\_URL} className="sitecore-article-card-image" />
     </div>
     <div className="sitecore-article-card-content">
     <a
     href=https://doc.sitecore.com/search/ja/developers/search-js-sdk-for-react/ui-components-reference/templates-for-content-items/recommendation/%7Bitem.url%7D
     onClick={(event) => {
     event.preventDefault();
     onItemClick({
     id: item.id,
     index,
     sourceId: item.source\_id,
     });
     }}
     className="sitecore-article-card-link"
     >
     <ArticleCard.Title className="sitecore-article-card-title">{item.name}</ArticleCard.Title>
     <ArticleCard.Subtitle className="sitecore-article-card-subtitle">
     {item.author}
     </ArticleCard.Subtitle>
     </a>
     </div>
     </ArticleCard.Root>
     ))}
     </div>
     </>
     )}
     </div>
     );
    };
    const RecommendationBasicVerticalWidget = widget(RecommendationBasicVerticalComponent, WidgetDataType.RECOMMENDATION, 'content');
    export default RecommendationBasicVerticalWidget;
    
    // styles.css
    @keyframes rotate-animation {
     from {
     transform: rotate(0deg);
     }
     to {
     transform: rotate(360deg);
     }
    }
    
    /\* ArticleCardRoot \*/
    .sitecore-article-card-root {
     cursor: pointer;
     box-sizing: border-box;
     box-shadow: 0 0 2px 2px var(--sdc-palette-grey-400);
     background: #fff;
     display: flex;
     flex-wrap: nowrap;
     margin: var(--sdc-spacing-m);
     max-height: 200px;
     padding: var(--sdc-spacing-m);
     position: relative;
     transition: 0.15s all ease;
     width: 100%;
    }
    
    .sitecore-article-card-root:focus-within {
     box-shadow: 0 0 8px 3px rgb(0 0 0 / 15%);
     transform: scale(1.025);
     opacity: 1;
     transition: 0.35s all ease;
    }
    
    .sitecore-article-card-root:hover {
     box-shadow: 0 0 8px 3px rgb(0 0 0 / 15%);
     transform: scale(1.025);
     opacity: 1;
     transition: 0.35s all ease;
    }
    
    /\* ArticleCardContent \*/
    .sitecore-article-card-content {
     flex-grow: 2;
     padding-left: var(--sdc-spacing-m);
    }
    
    /\* ArticleCardLink \*/
    .sitecore-article-card-link {
     color: var(--sdc-palette-text-primary);
     font-size: var(--sdc-typography-fontSize3-fontSize);
     text-decoration: none;
    }
    
    .sitecore-article-card-link:hover {
     text-decoration: none;
    }
    
    .sitecore-article-card-link:focus {
     text-decoration: none;
    }
    
    .sitecore-article-card-link::after {
     position: absolute;
     inset: 0;
     display: block;
     content: ' ';
    }
    
    /\* ArticleCardImage \*/
    .sitecore-article-card-image {
     width: 100%;
    }
    
    /\* ArticleCardImageWrapper \*/
    .sitecore-article-card-image-wrapper {
     position: relative;
     flex: 0 0 25%;
     max-width: 25%;
     height: 100%;
    }
    
    /\* ArticleCardTitle \*/
    .sitecore-article-card-title {
     color: var(--sdc-palette-text-primary);
     font-family: var(--sdc-typography-fontFamilySystem);
     font-size: var(--sdc-typography-h3-fontSize);
     font-weight: var(--sdc-typography-h3-fontWeight);
     line-height: var(--sdc-typography-h3-lineHeight);
     margin: 0 0 var(--sdc-spacing-m);
    }
    
    /\* ArticleCardSubtitle \*/
    .sitecore-article-card-subtitle {
     color: var(--sdc-palette-text-secondary);
     font-family: var(--sdc-typography-fontFamilySystem);
     font-size: var(--sdc-typography-fontSize3-fontSize);
     font-weight: var(--sdc-typography-fontSize3-fontWeight);
     line-height: var(--sdc-typography-fontSize3-lineHeight);
     margin: 0 0 var(--sdc-spacing-m);
    }
    
    /\* Title \*/
    .sitecore-title {
     color: var(--sdc-palette-primary-main);
     font-family: var(--sdc-typography-fontFamilySystem);
     font-size: var(--sdc-typography-h3-fontSize);
    }
    
    /\* Grid \*/
    .sitecore-grid {
     display: flex;
     flex-flow: row wrap;
    }
    
    /\* LoaderContainer \*/
    .sitecore-loader-container {
     align-items: center;
     display: flex;
     min-height: 50vh;
    }
    
    /\* RecommendationContainer \*/
    .sitecore-recommendation-container {
     max-width: 1280px;
     margin: 0 auto;
    }
    
    /\* LoaderAnimation \*/
    .sitecore-loader-animation {
     animation: rotate-animation 2s linear infinite;
     display: block;
     fill: var(--sdc-palette-primary-main);
     height: 50px;
     margin: auto;
     width: 50px;
    }
    

#### JS CLI { .notintoc }

Sitecore Search CLIを使えば、ウィジェットテンプレートの必要なファイルをReactプロジェクトに複製できます。以下の手順には、プロジェクトにCLIをインストールする手順と、ウィジェットコンポーネントを作成する場所を指定する手順が含まれています。

もし以前にCLIをプロジェクトにインストールしていて、.sc-search-settings.jsonというファイルに場所が定義されていれば、直接ステップ3に進めます。

プロジェクトにUI Components CLIをインストールするには:

1.  Search UI Components CLIをインストールするには、IDEまたはコマンドプロンプトのターミナルを開き、以下のコマンドを実行してください。
    
    npx install --save-dev @sitecore-search/cli
    
2.  ウィジェットコンポーネントが作成される場所を定義するには、プロジェクトのルートにあるファイル .sc-search-settings.jsonで、以下のコードブロックで示すように指定してください。
    
    {
     "components-path": "src-test/components"
    }
    
3.  index.jsxおよびstyles.cssファイルでウィジェットコンポーネントを作成するには、.sc-search-settings.jsonファイルで定義した場所に移動し、IDEまたはコマンドプロンプトのターミナルを開き、以下のコマンドを実行します。
    
    npx sc-search new-widget --language javascript --template recommendation-basic-vertical --entity content --styling css
    

#### JS manual { .notintoc }

Reactプロジェクトで手動でウィジェットテンプレートを作成する際は、必要な依存関係をインストールし、必要なファイルもすべて作成する必要があります。

プロジェクト内でこのウィジェットテンプレートを使ってウィジェットコンポーネントを作成するには:

1.  プロジェクト内で開発依存関係をインストールするには、IDEのターミナルまたはコマンドプロンプトを開き、以下のコマンドを実行します。
    
    npm install @radix-ui/react-icons@^1.3.0
    
2.  ウィジェットコンポーネント用のフォルダ内に「 index.jsx 」と「 styles.css 」というファイルを作成し、それぞれのファイルにコードブロックを貼り付けます。
    
    // index.jsx
    import { WidgetDataType, useRecommendation, widget } from '@sitecore-search/react';
    import { ArticleCard, Presence } from '@sitecore-search/ui';
    
    import './styles.css';
    
    const DEFAULT\_IMG\_URL = 'https://placehold.co/500x300?text=No%20Image'; // TODO: Update with corresponding fallback image
    
    export const RecommendationBasicVerticalComponent = ({ title = '', itemsToDisplay = 6 }) => {
     const {
     widgetRef,
     actions: { onItemClick },
     queryResult: { isLoading, isFetching, data: { content: articles =  } = {} },
     } = useRecommendation({
     state: {
     itemsPerPage: itemsToDisplay,
     },
     });
     const loading = isLoading || isFetching;
     return (
     <div className="sitecore-recommendation-container">
     <Presence present={loading}>
     <div className="sitecore-loader-container">
     <svg
     aria-busy={loading}
     aria-hidden={!loading}
     focusable="false"
     role="progressbar"
     viewBox="0 0 20 20"
     className="sitecore-loader-animation"
     >
     <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
     </svg>
     </div>
     </Presence>
     {!loading && (
     <>
     {title && <h3 className="sitecore-title">{title}</h3>}
     <div ref={widgetRef} className="sitecore-grid">
     {articles.map((item, index) => (
     <ArticleCard.Root key={item.id} className="sitecore-article-card-root">
     <div className="sitecore-article-card-image-wrapper">
     <ArticleCard.Image src={item?.image\_url || DEFAULT\_IMG\_URL} className="sitecore-article-card-image" />
     </div>
     <div className="sitecore-article-card-content">
     <a
     href=https://doc.sitecore.com/search/ja/developers/search-js-sdk-for-react/ui-components-reference/templates-for-content-items/recommendation/%7Bitem.url%7D
     onClick={(event) => {
     event.preventDefault();
     onItemClick({
     id: item.id,
     index,
     sourceId: item.source\_id,
     });
     }}
     className="sitecore-article-card-link"
     >
     <ArticleCard.Title className="sitecore-article-card-title">{item.name}</ArticleCard.Title>
     <ArticleCard.Subtitle className="sitecore-article-card-subtitle">
     {item.author}
     </ArticleCard.Subtitle>
     </a>
     </div>
     </ArticleCard.Root>
     ))}
     </div>
     </>
     )}
     </div>
     );
    };
    const RecommendationBasicVerticalWidget = widget(RecommendationBasicVerticalComponent, WidgetDataType.RECOMMENDATION, 'content');
    export default RecommendationBasicVerticalWidget;
    
    // styles.css
    @keyframes rotate-animation {
     from {
     transform: rotate(0deg);
     }
     to {
     transform: rotate(360deg);
     }
    }
    
    /\* ArticleCardRoot \*/
    .sitecore-article-card-root {
     cursor: pointer;
     box-sizing: border-box;
     box-shadow: 0 0 2px 2px var(--sdc-palette-grey-400);
     background: #fff;
     display: flex;
     flex-wrap: nowrap;
     margin: var(--sdc-spacing-m);
     max-height: 200px;
     padding: var(--sdc-spacing-m);
     position: relative;
     transition: 0.15s all ease;
     width: 100%;
    }
    
    .sitecore-article-card-root:focus-within {
     box-shadow: 0 0 8px 3px rgb(0 0 0 / 15%);
     transform: scale(1.025);
     opacity: 1;
     transition: 0.35s all ease;
    }
    
    .sitecore-article-card-root:hover {
     box-shadow: 0 0 8px 3px rgb(0 0 0 / 15%);
     transform: scale(1.025);
     opacity: 1;
     transition: 0.35s all ease;
    }
    
    /\* ArticleCardContent \*/
    .sitecore-article-card-content {
     flex-grow: 2;
     padding-left: var(--sdc-spacing-m);
    }
    
    /\* ArticleCardLink \*/
    .sitecore-article-card-link {
     color: var(--sdc-palette-text-primary);
     font-size: var(--sdc-typography-fontSize3-fontSize);
     text-decoration: none;
    }
    
    .sitecore-article-card-link:hover {
     text-decoration: none;
    }
    
    .sitecore-article-card-link:focus {
     text-decoration: none;
    }
    
    .sitecore-article-card-link::after {
     position: absolute;
     inset: 0;
     display: block;
     content: ' ';
    }
    
    /\* ArticleCardImage \*/
    .sitecore-article-card-image {
     width: 100%;
    }
    
    /\* ArticleCardImageWrapper \*/
    .sitecore-article-card-image-wrapper {
     position: relative;
     flex: 0 0 25%;
     max-width: 25%;
     height: 100%;
    }
    
    /\* ArticleCardTitle \*/
    .sitecore-article-card-title {
     color: var(--sdc-palette-text-primary);
     font-family: var(--sdc-typography-fontFamilySystem);
     font-size: var(--sdc-typography-h3-fontSize);
     font-weight: var(--sdc-typography-h3-fontWeight);
     line-height: var(--sdc-typography-h3-lineHeight);
     margin: 0 0 var(--sdc-spacing-m);
    }
    
    /\* ArticleCardSubtitle \*/
    .sitecore-article-card-subtitle {
     color: var(--sdc-palette-text-secondary);
     font-family: var(--sdc-typography-fontFamilySystem);
     font-size: var(--sdc-typography-fontSize3-fontSize);
     font-weight: var(--sdc-typography-fontSize3-fontWeight);
     line-height: var(--sdc-typography-fontSize3-lineHeight);
     margin: 0 0 var(--sdc-spacing-m);
    }
    
    /\* Title \*/
    .sitecore-title {
     color: var(--sdc-palette-primary-main);
     font-family: var(--sdc-typography-fontFamilySystem);
     font-size: var(--sdc-typography-h3-fontSize);
    }
    
    /\* Grid \*/
    .sitecore-grid {
     display: flex;
     flex-flow: row wrap;
    }
    
    /\* LoaderContainer \*/
    .sitecore-loader-container {
     align-items: center;
     display: flex;
     min-height: 50vh;
    }
    
    /\* RecommendationContainer \*/
    .sitecore-recommendation-container {
     max-width: 1280px;
     margin: 0 auto;
    }
    
    /\* LoaderAnimation \*/
    .sitecore-loader-animation {
     animation: rotate-animation 2s linear infinite;
     display: block;
     fill: var(--sdc-palette-primary-main);
     height: 50px;
     margin: auto;
     width: 50px;
    }
    

### CSSモジュールスタイルのコンポーネント

CSSモジュールスタイルのウィジェットテンプレートは、UIプリミティブを使用し、CSSモジュールでスタイリングされます。カスタマイズにはコンポーネントとCSSモジュールを編集できます。

#### TS CLI { .notintoc }

Sitecore Search CLIを使えば、ウィジェットテンプレートの必要なファイルをReactプロジェクトに複製できます。以下の手順には、プロジェクトにCLIをインストールする手順と、ウィジェットコンポーネントを作成する場所を指定する手順が含まれています。

もし以前にCLIをプロジェクトにインストールしていて、.sc-search-settings.jsonというファイルに場所が定義されていれば、直接ステップ3に進めます。

プロジェクトにUI Components CLIをインストールするには:

1.  Search UI Components CLIをインストールするには、IDEまたはコマンドプロンプトのターミナルを開き、以下のコマンドを実行してください。
    
    npx install --save-dev @sitecore-search/cli
    
2.  ウィジェットコンポーネントが作成される場所を定義するには、プロジェクトのルートにあるファイル .sc-search-settings.jsonで、以下のコードブロックで示すように指定してください。
    
    {
     "components-path": "src-test/components"
    }
    
3.  index.tsxおよびstyles.module.cssファイルでウィジェットコンポーネントを作成するには、.sc-search-settings.jsonファイルで定義した場所に移動し、IDEまたはコマンドプロンプトのターミナルを開き、以下のコマンドを実行します。
    
    npx sc-search new-widget --language typescript --template recommendation-basic-vertical --entity content --styling cssModule
    

#### TSマニュアル { .notintoc }

Reactプロジェクトで手動でウィジェットテンプレートを作成する際は、必要な依存関係をインストールし、必要なファイルもすべて作成する必要があります。

プロジェクト内でこのウィジェットテンプレートを使ってウィジェットコンポーネントを作成するには:

1.  プロジェクト内で開発依存関係をインストールするには、IDEのターミナルまたはコマンドプロンプトを開き、以下のコマンドを実行します。
    
    npm install @radix-ui/react-icons@^1.3.0
    
2.  ウィジェットコンポーネント用のフォルダ内に「 index.tsx 」と「 styles.module.css 」というファイルを作成し、それぞれのファイルにコードブロックを貼り付けます。
    
    // index.tsx
    import type { RecommendationInitialState } from '@sitecore-search/react';
    import { WidgetDataType, useRecommendation, widget } from '@sitecore-search/react';
    import { ArticleCard, Presence } from '@sitecore-search/ui';
    
    import styles from './styles.module.css';
    
    type ArticleModel = {
     id: string;
     name: string;
     author?: string;
     url?: string;
     image\_url?: string;
     source\_id?: string;
    };
    type RecommendationProps = {
     title?: string;
     itemsToDisplay?: number;
    };
    type InitialState = RecommendationInitialState<'itemsPerPage'>;
    const DEFAULT\_IMG\_URL = 'https://placehold.co/500x300?text=No%20Image'; // TODO: Update with corresponding fallback image
    
    export const RecommendationBasicVerticalComponent = ({ title = '', itemsToDisplay = 6 }: RecommendationProps) => {
     const {
     widgetRef,
     actions: { onItemClick },
     queryResult: { isLoading, isFetching, data: { content: articles =  } = {} },
     } = useRecommendation<ArticleModel, InitialState>({
     state: {
     itemsPerPage: itemsToDisplay,
     },
     });
     const loading = isLoading || isFetching;
     return (
     <div className={styles'sitecore-recommendation-container'}>
     <Presence present={loading}>
     <div className={styles'sitecore-loader-container'}>
     <svg
     aria-busy={loading}
     aria-hidden={!loading}
     focusable="false"
     role="progressbar"
     viewBox="0 0 20 20"
     className={styles'sitecore-loader-animation'}
     >
     <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
     </svg>
     </div>
     </Presence>
     {!loading && (
     <>
     {title && <h3 className={styles'sitecore-title'}>{title}</h3>}
     <div ref={widgetRef} className={styles'sitecore-grid'}>
     {articles.map((item, index) => (
     <ArticleCard.Root key={item.id} className={styles'sitecore-article-card-root'}>
     <div className={styles'sitecore-article-card-image-wrapper'}>
     <ArticleCard.Image
     src={item?.image\_url || DEFAULT\_IMG\_URL}
     className={styles'sitecore-article-card-image'}
     />
     </div>
     <div className={styles'sitecore-article-card-content'}>
     <a
     href=https://doc.sitecore.com/search/ja/developers/search-js-sdk-for-react/ui-components-reference/templates-for-content-items/recommendation/%7Bitem.url%7D
     onClick={(event) => {
     event.preventDefault();
     onItemClick({
     id: item.id,
     index,
     sourceId: item.source\_id,
     });
     }}
     className={styles'sitecore-article-card-link'}
     >
     <ArticleCard.Title className={styles'sitecore-article-card-title'}>{item.name}</ArticleCard.Title>
     <ArticleCard.Subtitle className={styles'sitecore-article-card-subtitle'}>
     {item.author}
     </ArticleCard.Subtitle>
     </a>
     </div>
     </ArticleCard.Root>
     ))}
     </div>
     </>
     )}
     </div>
     );
    };
    const RecommendationBasicVerticalWidget = widget(RecommendationBasicVerticalComponent, WidgetDataType.RECOMMENDATION, 'content');
    export default RecommendationBasicVerticalWidget;
    
    // styles.module.css
    @keyframes rotate-animation {
     from {
     transform: rotate(0deg);
     }
     to {
     transform: rotate(360deg);
     }
    }
    
    /\* ArticleCardRoot \*/
    .sitecore-article-card-root {
     cursor: pointer;
     box-sizing: border-box;
     box-shadow: 0 0 2px 2px var(--sdc-palette-grey-400);
     background: #fff;
     display: flex;
     flex-wrap: nowrap;
     margin: var(--sdc-spacing-m);
     max-height: 200px;
     padding: var(--sdc-spacing-m);
     position: relative;
     transition: 0.15s all ease;
     width: 100%;
    }
    
    .sitecore-article-card-root:focus-within {
     box-shadow: 0 0 8px 3px rgb(0 0 0 / 15%);
     transform: scale(1.025);
     opacity: 1;
     transition: 0.35s all ease;
    }
    
    .sitecore-article-card-root:hover {
     box-shadow: 0 0 8px 3px rgb(0 0 0 / 15%);
     transform: scale(1.025);
     opacity: 1;
     transition: 0.35s all ease;
    }
    
    /\* ArticleCardContent \*/
    .sitecore-article-card-content {
     flex-grow: 2;
     padding-left: var(--sdc-spacing-m);
    }
    
    /\* ArticleCardLink \*/
    .sitecore-article-card-link {
     color: var(--sdc-palette-text-primary);
     font-size: var(--sdc-typography-fontSize3-fontSize);
     text-decoration: none;
    }
    
    .sitecore-article-card-link:hover {
     text-decoration: none;
    }
    
    .sitecore-article-card-link:focus {
     text-decoration: none;
    }
    
    .sitecore-article-card-link::after {
     position: absolute;
     inset: 0;
     display: block;
     content: ' ';
    }
    
    /\* ArticleCardImage \*/
    .sitecore-article-card-image {
     width: 100%;
    }
    
    /\* ArticleCardImageWrapper \*/
    .sitecore-article-card-image-wrapper {
     position: relative;
     flex: 0 0 25%;
     max-width: 25%;
     height: 100%;
    }
    
    /\* ArticleCardTitle \*/
    .sitecore-article-card-title {
     color: var(--sdc-palette-text-primary);
     font-family: var(--sdc-typography-fontFamilySystem);
     font-size: var(--sdc-typography-h3-fontSize);
     font-weight: var(--sdc-typography-h3-fontWeight);
     line-height: var(--sdc-typography-h3-lineHeight);
     margin: 0 0 var(--sdc-spacing-m);
    }
    
    /\* ArticleCardSubtitle \*/
    .sitecore-article-card-subtitle {
     color: var(--sdc-palette-text-secondary);
     font-family: var(--sdc-typography-fontFamilySystem);
     font-size: var(--sdc-typography-fontSize3-fontSize);
     font-weight: var(--sdc-typography-fontSize3-fontWeight);
     line-height: var(--sdc-typography-fontSize3-lineHeight);
     margin: 0 0 var(--sdc-spacing-m);
    }
    
    /\* Title \*/
    .sitecore-title {
     color: var(--sdc-palette-primary-main);
     font-family: var(--sdc-typography-fontFamilySystem);
     font-size: var(--sdc-typography-h3-fontSize);
    }
    
    /\* Grid \*/
    .sitecore-grid {
     display: flex;
     flex-flow: row wrap;
    }
    
    /\* LoaderContainer \*/
    .sitecore-loader-container {
     align-items: center;
     display: flex;
     min-height: 50vh;
    }
    
    /\* RecommendationContainer \*/
    .sitecore-recommendation-container {
     max-width: 1280px;
     margin: 0 auto;
    }
    
    /\* LoaderAnimation \*/
    .sitecore-loader-animation {
     animation: rotate-animation 2s linear infinite;
     display: block;
     fill: var(--sdc-palette-primary-main);
     height: 50px;
     margin: auto;
     width: 50px;
    }
    

#### JS CLI { .notintoc }

Sitecore Search CLIを使えば、ウィジェットテンプレートの必要なファイルをReactプロジェクトに複製できます。以下の手順には、プロジェクトにCLIをインストールする手順と、ウィジェットコンポーネントを作成する場所を指定する手順が含まれています。

もし以前にCLIをプロジェクトにインストールしていて、.sc-search-settings.jsonというファイルに場所が定義されていれば、直接ステップ3に進めます。

プロジェクトにUI Components CLIをインストールするには:

1.  Search UI Components CLIをインストールするには、IDEまたはコマンドプロンプトのターミナルを開き、以下のコマンドを実行してください。
    
    npx install --save-dev @sitecore-search/cli
    
2.  ウィジェットコンポーネントが作成される場所を定義するには、プロジェクトのルートにあるファイル .sc-search-settings.jsonで、以下のコードブロックで示すように指定してください。
    
    {
     "components-path": "src-test/components"
    }
    
3.  index.jsxおよびstyles.module.cssファイルでウィジェットコンポーネントを作成するには、.sc-search-settings.jsonファイルで定義した場所に移動し、IDEまたはコマンドプロンプトのターミナルを開き、以下のコマンドを実行します。
    
    npx sc-search new-widget --language javascript --template recommendation-basic-vertical --entity content --styling cssModule
    

#### JS manual { .notintoc }

Reactプロジェクトで手動でウィジェットテンプレートを作成する際は、必要な依存関係をインストールし、必要なファイルもすべて作成する必要があります。

プロジェクト内でこのウィジェットテンプレートを使ってウィジェットコンポーネントを作成するには:

1.  プロジェクト内で開発依存関係をインストールするには、IDEのターミナルまたはコマンドプロンプトを開き、以下のコマンドを実行します。
    
    npm install @radix-ui/react-icons@^1.3.0
    
2.  ウィジェットコンポーネント用のフォルダ内に「 index.jsx 」と「 styles.module.css 」というファイルを作成し、それぞれのファイルにコードブロックを貼り付けます。
    
    // index.jsx
    import { WidgetDataType, useRecommendation, widget } from '@sitecore-search/react';
    import { ArticleCard, Presence } from '@sitecore-search/ui';
    
    import styles from './styles.module.css';
    
    const DEFAULT\_IMG\_URL = 'https://placehold.co/500x300?text=No%20Image'; // TODO: Update with corresponding fallback image
    
    export const RecommendationBasicVerticalComponent = ({ title = '', itemsToDisplay = 6 }) => {
     const {
     widgetRef,
     actions: { onItemClick },
     queryResult: { isLoading, isFetching, data: { content: articles =  } = {} },
     } = useRecommendation({
     state: {
     itemsPerPage: itemsToDisplay,
     },
     });
     const loading = isLoading || isFetching;
     return (
     <div className={styles'sitecore-recommendation-container'}>
     <Presence present={loading}>
     <div className={styles'sitecore-loader-container'}>
     <svg
     aria-busy={loading}
     aria-hidden={!loading}
     focusable="false"
     role="progressbar"
     viewBox="0 0 20 20"
     className={styles'sitecore-loader-animation'}
     >
     <path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" />
     </svg>
     </div>
     </Presence>
     {!loading && (
     <>
     {title && <h3 className={styles'sitecore-title'}>{title}</h3>}
     <div ref={widgetRef} className={styles'sitecore-grid'}>
     {articles.map((item, index) => (
     <ArticleCard.Root key={item.id} className={styles'sitecore-article-card-root'}>
     <div className={styles'sitecore-article-card-image-wrapper'}>
     <ArticleCard.Image
     src={item?.image\_url || DEFAULT\_IMG\_URL}
     className={styles'sitecore-article-card-image'}
     />
     </div>
     <div className={styles'sitecore-article-card-content'}>
     <a
     href=https://doc.sitecore.com/search/ja/developers/search-js-sdk-for-react/ui-components-reference/templates-for-content-items/recommendation/%7Bitem.url%7D
     onClick={(event) => {
     event.preventDefault();
     onItemClick({
     id: item.id,
     index,
     sourceId: item.source\_id,
     });
     }}
     className={styles'sitecore-article-card-link'}
     >
     <ArticleCard.Title className={styles'sitecore-article-card-title'}>{item.name}</ArticleCard.Title>
     <ArticleCard.Subtitle className={styles'sitecore-article-card-subtitle'}>
     {item.author}
     </ArticleCard.Subtitle>
     </a>
     </div>
     </ArticleCard.Root>
     ))}
     </div>
     </>
     )}
     </div>
     );
    };
    const RecommendationBasicVerticalWidget = widget(RecommendationBasicVerticalComponent, WidgetDataType.RECOMMENDATION, 'content');
    export default RecommendationBasicVerticalWidget;
    
    // styles.module.css
    @keyframes rotate-animation {
     from {
     transform: rotate(0deg);
     }
     to {
     transform: rotate(360deg);
     }
    }
    
    /\* ArticleCardRoot \*/
    .sitecore-article-card-root {
     cursor: pointer;
     box-sizing: border-box;
     box-shadow: 0 0 2px 2px var(--sdc-palette-grey-400);
     background: #fff;
     display: flex;
     flex-wrap: nowrap;
     margin: var(--sdc-spacing-m);
     max-height: 200px;
     padding: var(--sdc-spacing-m);
     position: relative;
     transition: 0.15s all ease;
     width: 100%;
    }
    
    .sitecore-article-card-root:focus-within {
     box-shadow: 0 0 8px 3px rgb(0 0 0 / 15%);
     transform: scale(1.025);
     opacity: 1;
     transition: 0.35s all ease;
    }
    
    .sitecore-article-card-root:hover {
     box-shadow: 0 0 8px 3px rgb(0 0 0 / 15%);
     transform: scale(1.025);
     opacity: 1;
     transition: 0.35s all ease;
    }
    
    /\* ArticleCardContent \*/
    .sitecore-article-card-content {
     flex-grow: 2;
     padding-left: var(--sdc-spacing-m);
    }
    
    /\* ArticleCardLink \*/
    .sitecore-article-card-link {
     color: var(--sdc-palette-text-primary);
     font-size: var(--sdc-typography-fontSize3-fontSize);
     text-decoration: none;
    }
    
    .sitecore-article-card-link:hover {
     text-decoration: none;
    }
    
    .sitecore-article-card-link:focus {
     text-decoration: none;
    }
    
    .sitecore-article-card-link::after {
     position: absolute;
     inset: 0;
     display: block;
     content: ' ';
    }
    
    /\* ArticleCardImage \*/
    .sitecore-article-card-image {
     width: 100%;
    }
    
    /\* ArticleCardImageWrapper \*/
    .sitecore-article-card-image-wrapper {
     position: relative;
     flex: 0 0 25%;
     max-width: 25%;
     height: 100%;
    }
    
    /\* ArticleCardTitle \*/
    .sitecore-article-card-title {
     color: var(--sdc-palette-text-primary);
     font-family: var(--sdc-typography-fontFamilySystem);
     font-size: var(--sdc-typography-h3-fontSize);
     font-weight: var(--sdc-typography-h3-fontWeight);
     line-height: var(--sdc-typography-h3-lineHeight);
     margin: 0 0 var(--sdc-spacing-m);
    }
    
    /\* ArticleCardSubtitle \*/
    .sitecore-article-card-subtitle {
     color: var(--sdc-palette-text-secondary);
     font-family: var(--sdc-typography-fontFamilySystem);
     font-size: var(--sdc-typography-fontSize3-fontSize);
     font-weight: var(--sdc-typography-fontSize3-fontWeight);
     line-height: var(--sdc-typography-fontSize3-lineHeight);
     margin: 0 0 var(--sdc-spacing-m);
    }
    
    /\* Title \*/
    .sitecore-title {
     color: var(--sdc-palette-primary-main);
     font-family: var(--sdc-typography-fontFamilySystem);
     font-size: var(--sdc-typography-h3-fontSize);
    }
    
    /\* Grid \*/
    .sitecore-grid {
     display: flex;
     flex-flow: row wrap;
    }
    
    /\* LoaderContainer \*/
    .sitecore-loader-container {
     align-items: center;
     display: flex;
     min-height: 50vh;
    }
    
    /\* RecommendationContainer \*/
    .sitecore-recommendation-container {
     max-width: 1280px;
     margin: 0 auto;
    }
    
    /\* LoaderAnimation \*/
    .sitecore-loader-animation {
     animation: rotate-animation 2s linear infinite;
     display: block;
     fill: var(--sdc-palette-primary-main);
     height: 50px;
     margin: auto;
     width: 50px;
    }
## オプション

### 展示品

itemsToDisplayこのコンポーネントに表示される最大アイテム数を設定します。

デフォルト:6

<RecommendationCarousel
	rfkId="rfkid\_2"
 	title="Latest news"
 	itemsToDisplay={12}
/>