| @@ -1,17 +1,18 @@ | ||
| 1 | 1 | import classnames from 'classnames' |
| 2 | 2 | import { __ } from '@wordpress/i18n' |
| 3 | 3 | import React from 'react' |
| 4 | 4 | import { isLicensed } from '../../utils/screen' |
| 5 | +import { useHorizontalScrollOverflow } from '../../hooks/useHorizontalScrollOverflow' | |
| 5 | 6 | import { fetchQueryParam, updateQueryParams } from '../../utils/urls' |
| 6 | 7 | |
| 7 | -export interface SubnavTab { | |
| 8 | - name: string | |
| 8 | +export interface SubnavTab<T extends string> { | |
| 9 | + name: T | |
| 9 | 10 | label: string |
| 10 | 11 | pro?: boolean |
| 11 | 12 | } |
| 12 | 13 | |
| 13 | -export const getTabFromQuery = <Tab extends SubnavTab>( | |
| 14 | +export const getTabFromQuery = <Tab extends SubnavTab<string>>( | |
| 14 | 15 | tabs: Tab[], |
| 15 | 16 | queryParamName: string |
| 16 | 17 | ): Tab => { |
| 17 | 18 | const tabParam = fetchQueryParam(queryParamName) |
| @@ -24,9 +25,50 @@ | ||
| 24 | 25 | |
| 25 | 26 | return tabs[0] |
| 26 | 27 | } |
| 27 | 28 | |
| 28 | -export interface SubnavTabsProps<Tab extends SubnavTab> { | |
| 29 | +interface SubnavTabItemProps<Tab extends SubnavTab<string>> { | |
| 30 | + tab: Tab | |
| 31 | + currentTab: Tab | |
| 32 | + getTabCount?: (tab: Tab) => number | undefined | |
| 33 | + setCurrentTab: (tab: Tab) => void | |
| 34 | + queryParamName?: string | |
| 35 | +} | |
| 36 | + | |
| 37 | +const SubnavTabItem = <Tab extends SubnavTab<string>>({ | |
| 38 | + tab, | |
| 39 | + currentTab, | |
| 40 | + getTabCount, | |
| 41 | + setCurrentTab, | |
| 42 | + queryParamName | |
| 43 | +}: SubnavTabItemProps<Tab>) => { | |
| 44 | + const count = getTabCount?.(tab) | |
| 45 | + | |
| 46 | + return ( | |
| 47 | + <li> | |
| 48 | + <button | |
| 49 | + type="button" | |
| 50 | + className={classnames('snippet-type-link', `${tab.name}-subnav-link`, { | |
| 51 | + 'active-type': tab.name === currentTab.name | |
| 52 | + })} | |
| 53 | + aria-current={tab.name === currentTab.name ? 'page' : undefined} | |
| 54 | + onClick={() => { | |
| 55 | + setCurrentTab(tab) | |
| 56 | + | |
| 57 | + if (queryParamName) { | |
| 58 | + updateQueryParams({ [queryParamName]: tab.name }) | |
| 59 | + } | |
| 60 | + }} | |
| 61 | + > | |
| 62 | + <span>{tab.label}</span> | |
| 63 | + {count !== undefined && <span className="subnav-count">{count}</span>} | |
| 64 | + {tab.pro && !isLicensed() && <span className="pro-chip">{__('Pro', 'code-snippets')}</span>} | |
| 65 | + </button> | |
| 66 | + </li> | |
| 67 | + ) | |
| 68 | +} | |
| 69 | + | |
| 70 | +export interface SubnavTabsProps<Tab extends SubnavTab<string>> { | |
| 29 | 71 | tabs: readonly Tab[] |
| 30 | 72 | ariaLabel: string |
| 31 | 73 | className?: string |
| 32 | 74 | currentTab: Tab |
| @@ -34,9 +76,9 @@ | ||
| 34 | 76 | setCurrentTab: (tab: Tab) => void |
| 35 | 77 | queryParamName?: string |
| 36 | 78 | } |
| 37 | 79 | |
| 38 | -export const SubnavTabs = <Tab extends SubnavTab>({ | |
| 80 | +export const SubnavTabs = <Tab extends SubnavTab<string>>({ | |
| 39 | 81 | tabs, |
| 40 | 82 | ariaLabel, |
| 41 | 83 | className, |
| 42 | 84 | currentTab, |
| @@ -41,36 +83,31 @@ | ||
| 41 | 83 | className, |
| 42 | 84 | currentTab, |
| 43 | 85 | getTabCount, |
| 44 | 86 | setCurrentTab, |
| 45 | - queryParamName, | |
| 46 | -}: SubnavTabsProps<Tab>) => | |
| 47 | - <nav className={classnames('snippet-type-nav', className)} aria-label={ariaLabel}> | |
| 48 | - <ul> | |
| 49 | - {tabs.map(tab => { | |
| 50 | - const count = getTabCount?.(tab) | |
| 87 | + queryParamName | |
| 88 | +}: SubnavTabsProps<Tab>) => { | |
| 89 | + const { atStart, atEnd, scrollRef } = useHorizontalScrollOverflow() | |
| 51 | 90 | |
| 52 | - return ( | |
| 53 | - <li key={tab.name}> | |
| 54 | - <button | |
| 55 | - type="button" | |
| 56 | - className={classnames('snippet-type-link', `${tab.name}-subnav-link`, { | |
| 57 | - 'active-type': tab.name === currentTab.name | |
| 58 | - })} | |
| 59 | - aria-current={tab.name === currentTab.name ? 'page' : undefined} | |
| 60 | - onClick={() => { | |
| 61 | - setCurrentTab(tab) | |
| 62 | - | |
| 63 | - if (queryParamName) { | |
| 64 | - updateQueryParams({ [queryParamName]: tab.name }) | |
| 65 | - } | |
| 66 | - }} | |
| 67 | - > | |
| 68 | - <span>{tab.label}</span> | |
| 69 | - {count && <span className="subnav-count">{count}</span>} | |
| 70 | - {tab.pro && !isLicensed() && <span className="pro-chip">{__('Pro', 'code-snippets')}</span>} | |
| 71 | - </button> | |
| 72 | - </li> | |
| 73 | - ) | |
| 91 | + return ( | |
| 92 | + <div | |
| 93 | + className={classnames('snippet-type-nav-wrapper', { | |
| 94 | + 'has-scroll-start': !atStart, | |
| 95 | + 'has-scroll-end': !atEnd | |
| 74 | 96 | })} |
| 75 | - </ul> | |
| 76 | - </nav> | |
| 97 | + > | |
| 98 | + <nav ref={scrollRef} className={classnames('snippet-type-nav', className)} aria-label={ariaLabel}> | |
| 99 | + <ul> | |
| 100 | + {tabs.map(tab => | |
| 101 | + <SubnavTabItem | |
| 102 | + key={tab.name} | |
| 103 | + tab={tab} | |
| 104 | + currentTab={currentTab} | |
| 105 | + getTabCount={getTabCount} | |
| 106 | + setCurrentTab={setCurrentTab} | |
| 107 | + queryParamName={queryParamName} | |
| 108 | + />)} | |
| 109 | + </ul> | |
| 110 | + </nav> | |
| 111 | + </div> | |
| 112 | + ) | |
| 113 | +} | |