From 531c89e3584a0a02655aa116cb8f8157a58bde8f Mon Sep 17 00:00:00 2001 From: Daichi Igarashi Date: Thu, 12 Oct 2023 17:36:57 +0900 Subject: [PATCH 1/4] feat(spindle-ui): add helper to get rel attribute of each anchor elements --- .../helpers/getLinkRelAttribute.test.ts | 49 +++++++++++++++++++ .../Pagination/helpers/getLinkRelAttribute.ts | 17 +++++++ 2 files changed, 66 insertions(+) create mode 100644 packages/spindle-ui/src/Pagination/helpers/getLinkRelAttribute.test.ts create mode 100644 packages/spindle-ui/src/Pagination/helpers/getLinkRelAttribute.ts diff --git a/packages/spindle-ui/src/Pagination/helpers/getLinkRelAttribute.test.ts b/packages/spindle-ui/src/Pagination/helpers/getLinkRelAttribute.test.ts new file mode 100644 index 000000000..58ec5f86d --- /dev/null +++ b/packages/spindle-ui/src/Pagination/helpers/getLinkRelAttribute.test.ts @@ -0,0 +1,49 @@ +import { getLinkRelAttribute } from './getLinkRelAttribute'; + +const NOFOLLOW = 'nofollow'; + +describe('getLinkRelAttribute', () => { + it('should return undefined when linkFollowType is all', () => { + const linkFollowType = 'all'; + const pageNumber = 1; + + const linkRelAttribute = getLinkRelAttribute({ + linkFollowType, + pageNumber, + }); + expect(linkRelAttribute).toEqual(undefined); + }); + + it('should return nofollow when linkFollowType is none', () => { + const linkFollowType = 'none'; + const pageNumber = 1; + + const linkRelAttribute = getLinkRelAttribute({ + linkFollowType, + pageNumber, + }); + expect(linkRelAttribute).toEqual(NOFOLLOW); + }); + + it('should return undefined when linkFollowType is firstPage and pageNumber is 1', () => { + const linkFollowType = 'firstPage'; + const pageNumber = 1; + + const linkRelAttribute = getLinkRelAttribute({ + linkFollowType, + pageNumber, + }); + expect(linkRelAttribute).toEqual(undefined); + }); + + it('should return nofollow when linkFollowType is firstPage and pageNumber is not 1', () => { + const linkFollowType = 'firstPage'; + const pageNumber = 2; + + const linkRelAttribute = getLinkRelAttribute({ + linkFollowType, + pageNumber, + }); + expect(linkRelAttribute).toEqual(NOFOLLOW); + }); +}); diff --git a/packages/spindle-ui/src/Pagination/helpers/getLinkRelAttribute.ts b/packages/spindle-ui/src/Pagination/helpers/getLinkRelAttribute.ts new file mode 100644 index 000000000..87ea83440 --- /dev/null +++ b/packages/spindle-ui/src/Pagination/helpers/getLinkRelAttribute.ts @@ -0,0 +1,17 @@ +import type { LinkFollowType } from '../Pagination'; + +type Payload = { + linkFollowType: LinkFollowType; + pageNumber: number; +}; + +export function getLinkRelAttribute({ linkFollowType, pageNumber }: Payload) { + switch (linkFollowType) { + case 'all': + return undefined; + case 'none': + return 'nofollow'; + case 'firstPage': + return pageNumber === 1 ? undefined : 'nofollow'; + } +} From e0a6213b448033f6a680e127e1733cf4cef2b63f Mon Sep 17 00:00:00 2001 From: Daichi Igarashi Date: Thu, 12 Oct 2023 17:38:56 +0900 Subject: [PATCH 2/4] feat(spindle-ui): add linkFollowType props in Pagination BREAKING CHANGE: Pagination requires the 'linkFollowType' property to be set. --- packages/spindle-ui/src/Pagination/Pagination.tsx | 12 ++++++++++-- .../spindle-ui/src/Pagination/PaginationItem.tsx | 14 ++++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/spindle-ui/src/Pagination/Pagination.tsx b/packages/spindle-ui/src/Pagination/Pagination.tsx index 77793bca6..1e3676caa 100644 --- a/packages/spindle-ui/src/Pagination/Pagination.tsx +++ b/packages/spindle-ui/src/Pagination/Pagination.tsx @@ -3,10 +3,14 @@ import MenuHorizontal from '../Icon/MenuHorizontal'; import PaginationItem from './PaginationItem'; import { useShowItem } from './hooks/useShowItem'; +import { getLinkRelAttribute } from './helpers/getLinkRelAttribute'; + +export type LinkFollowType = 'all' | 'none' | 'firstPage'; interface Props extends React.HTMLAttributes { current: number; total: number; + linkFollowType: LinkFollowType; showTotal?: boolean; onPageChange?: ( event: React.MouseEvent, @@ -27,6 +31,7 @@ export const Pagination = (props: Props) => { const { current, total, + linkFollowType, showTotal = false, onPageChange, createUrl, @@ -103,6 +108,7 @@ export const Pagination = (props: Props) => { { { )} {displayItem.map((pageNumber, index) => { const isCurrent = current === pageNumber; - const hasRelAttribute = current === pageNumber + 1; // 数字が隣接していない場合に表示 const showEllipsis = @@ -135,7 +141,7 @@ export const Pagination = (props: Props) => { > { { , pageNumber: number, @@ -20,7 +23,14 @@ type Props = { const BLOCK_NAME = 'spui-PaginationItem'; export const PaginationItem: FC = React.memo( - function PaginationItem({ type, current, total, onClick, createUrl }) { + function PaginationItem({ + type, + current, + total, + linkFollowType, + onClick, + createUrl, + }) { const isDisabled = type === 'first' || type === 'prev' ? current === 1 : current === total; const isShowLabel = type === 'prev' || type === 'next'; @@ -81,7 +91,7 @@ export const PaginationItem: FC = React.memo( return ( Date: Thu, 12 Oct 2023 17:40:19 +0900 Subject: [PATCH 3/4] test(spindle-ui): add test of Pagination about rel attribute --- .../src/Pagination/Pagination.test.tsx | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/packages/spindle-ui/src/Pagination/Pagination.test.tsx b/packages/spindle-ui/src/Pagination/Pagination.test.tsx index 5d5cc694d..7b34674e7 100644 --- a/packages/spindle-ui/src/Pagination/Pagination.test.tsx +++ b/packages/spindle-ui/src/Pagination/Pagination.test.tsx @@ -14,6 +14,7 @@ describe('', () => { `/detail/${pageNumber}.html`} onPageChange={onClick} @@ -24,4 +25,63 @@ describe('', () => { fireEvent.click(screen.getByText(1)); expect(onClick).toBeCalled(); }); + + test('should render all anchors without rel attribute when linkFollowType is all', () => { + render( + `/detail/${pageNumber}.html`} + onPageChange={() => {}} + />, + ); + + const anchors = screen.getAllByRole('link'); + anchors.forEach((anchor) => { + expect(anchor).not.toHaveAttribute('rel'); + }); + }); + + test('should render all anchors with rel="nofollow" when linkFollowType is none', () => { + render( + `/detail/${pageNumber}.html`} + onPageChange={() => {}} + />, + ); + + const anchors = screen.getAllByRole('link'); + anchors.forEach((anchor) => { + expect(anchor).toHaveAttribute('rel', 'nofollow'); + }); + }); + + test('should render anchors for first page without rel attribute and others with rel="nofollow" when linkFollowType is firstPage', () => { + render( + `/detail/${pageNumber}.html`} + onPageChange={() => {}} + />, + ); + + const anchors = screen.getAllByRole('link'); + anchors.forEach((anchor) => { + const isFirstPage = anchor.getAttribute('href') === '/detail/1.html'; + if (isFirstPage) { + expect(anchor).not.toHaveAttribute('rel'); + } else { + expect(anchor).toHaveAttribute('rel', 'nofollow'); + } + }); + }); }); From 02dbb9eaa1e6db046010c28e708977948d6a814e Mon Sep 17 00:00:00 2001 From: Daichi Igarashi Date: Fri, 13 Oct 2023 11:44:07 +0900 Subject: [PATCH 4/4] docs(spindle-ui): add story of Pagination --- .../src/Pagination/Pagination.stories.mdx | 157 ++++++++++++++++-- 1 file changed, 139 insertions(+), 18 deletions(-) diff --git a/packages/spindle-ui/src/Pagination/Pagination.stories.mdx b/packages/spindle-ui/src/Pagination/Pagination.stories.mdx index 07a3db386..b0fd221f7 100644 --- a/packages/spindle-ui/src/Pagination/Pagination.stories.mdx +++ b/packages/spindle-ui/src/Pagination/Pagination.stories.mdx @@ -30,15 +30,19 @@ import { Pagination } from './Pagination'; - `current`(必須): 現在のページ数を指定してください。 - `total`(必須): 総ページ数を指定してください。 - - `createUrl`(必須): 関数を渡すことでリンクのhrefとなる値を生成することが可能です。 + - `linkFollowType`(必須): リンクのrelに設定する値を`all`, `none`, `firstPage` + から選択できます。詳しくは[linkFollowTypeの設定](#link-follow-type-settings)を参照してください。 + + + - `createUrl`(必須): + 関数を渡すことでリンクのhrefとなる値を生成することが可能です。 - `showTotal`(任意): 現在のページ数と総ページ数を表示します。デフォルト値はfalseです。 - - `onPageChange`(任意): - リンクをクリック後に処理をしたい場合に利用できます。 + - `onPageChange`(任意): リンクをクリック後に処理をしたい場合に利用できます。 export const url = '/detail/CURRENT.html'; @@ -54,7 +58,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} />' + ' `/detail/${pageNumber}.html`} />' } /> @@ -65,6 +69,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -75,7 +80,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} />' + ' `/detail/${pageNumber}.html`} />' } /> @@ -86,6 +91,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -99,6 +105,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -109,7 +116,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} />' + ' `/detail/${pageNumber}.html`} />' } /> @@ -120,6 +127,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -133,6 +141,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -146,6 +155,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -156,7 +166,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} />' + ' `/detail/${pageNumber}.html`} />' } /> @@ -167,6 +177,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -180,6 +191,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -193,6 +205,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -206,6 +219,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -216,7 +230,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} />' + ' `/detail/${pageNumber}.html`} />' } /> @@ -227,6 +241,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -240,6 +255,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -253,6 +269,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -266,6 +283,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -279,6 +297,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -295,7 +314,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} />' + ' `/detail/${pageNumber}.html`} />' } /> @@ -306,13 +325,13 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> - #### current 2 @@ -320,6 +339,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -333,6 +353,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -346,6 +367,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -359,13 +381,13 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> - #### current 6 @@ -373,6 +395,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -383,7 +406,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} />' + ' `/detail/${pageNumber}.html`} />' } /> @@ -394,6 +417,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -407,6 +431,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -420,6 +445,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -433,6 +459,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -446,6 +473,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -459,6 +487,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -472,6 +501,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -486,7 +516,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} />' + ' `/detail/${pageNumber}.html`} />' } /> @@ -497,6 +527,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -510,6 +541,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -523,6 +555,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -536,6 +569,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -549,6 +583,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -562,6 +597,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -575,6 +611,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -588,6 +625,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -601,6 +639,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} /> @@ -618,6 +657,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} {...actions('onClick')} @@ -627,7 +667,7 @@ export const pageNumber = 1; `/detail/${pageNumber}.html`} />' + ' `/detail/${pageNumber}.html`} />' } /> @@ -646,19 +686,100 @@ export const handleClick = (event, pageNumber) => { `/detail/${pageNumber}.html`} - onPageChange={(event, pageNumber) => { handleClick(event, pageNumber); }} + onPageChange={(event, pageNumber) => { + handleClick(event, pageNumber); + }} {...actions('onClick')} /> { event.preventDefault(); // router.push などで遷移処理を記述 }; -` +' `/detail/${pageNumber}.html`} onPageChange={(event, pageNumber) => { handleClick(event, pageNumber); }} />'+ ` -`} +` + + ' `/detail/${pageNumber}.html`} onPageChange={(event, pageNumber) => { handleClick(event, pageNumber); }} />' + + ` +` + } +/> + + + +### 全てのリンクをdofollowにする + + + linkFollowTypeをallに指定することで、全てのページ遷移リンクにrel属性が設定されません。全てのページをクロールして欲しい場合に指定します。 + + + + + `/detail/${pageNumber}.html`} + {...actions('onClick')} + /> + + + + `/detail/${pageNumber}.html`} />' + } +/> + +### 全てのリンクをnofollowにする + + + linkFollowTypeをnoneに指定することで、全てのページ遷移リンクのrel属性にnofollowが設定されます。いずれのページもクロールして欲しくない場合に指定します。 + + + + + `/detail/${pageNumber}.html`} + {...actions('onClick')} + /> + + + + `/detail/${pageNumber}.html`} />' + } +/> + +### 1ページ目のリンク以外をnofollowにする + + + linkFollowTypeをfirstPageに指定することで、1ページ目への遷移リンク以外のrel属性にnofollowが設定されます。1ページ目のみをクロールして欲しい場合に指定します。 + + + + + `/detail/${pageNumber}.html`} + {...actions('onClick')} + /> + + + + `/detail/${pageNumber}.html`} />' + } />