PluginProbe
Code Snippets / trunk
Code Snippets vtrunk
4.0.0-beta.2 3.10.2 3.10.1 3.10.0 3.10.0-beta.2 3.10.0-beta.1 4.0.0-beta.1 3.9.6 trunk 2.10.0 2.10.1 2.12.0 2.12.1 2.13.0 2.13.1 2.13.2 2.13.3 2.14.0 2.14.1 2.14.2 2.14.3 2.14.4 2.14.5 2.14.6 3.0.0 All 65 releases
← All changes | js/components/common/Toolbar.tsx +208 -148 3.10.0-beta.1 → trunk View file →
@@ -1,178 +1,238 @@
1 1 import { __, _x } from '@wordpress/i18n'
2 2 import classnames from 'classnames'
3 -import React, { useState } from 'react'
3 +import React, { useMemo, useState } from 'react'
4 4 import { isLicensed, shouldShowUpsell } from '../../utils/screen'
5 -import { buildUrl, fetchQueryParam } from '../../utils/urls'
6 -import { BlueprintIcon, CommunityIcon, LibraryIcon, SettingsIcon, SnippetsIcon } from './icons/ToolbarIcons'
5 +import { buildUrl, fetchConstQueryParam } from '../../utils/urls'
6 +import { SUBPAGES, SUBPAGE_ENTRIES, type SubpageName } from '../ManageMenu/subpages'
7 +import { hasSeenDemo } from './demo/useDemoSeen'
8 +import { AiAgentIcon } from './icons/AiAgentIcon'
9 +import { BlueprintIcon } from './icons/BlueprintIcon'
10 +import { CommunityIcon } from './icons/CommunityIcon'
11 +import { LibraryIcon } from './icons/LibraryIcon'
12 +import { SettingsIcon } from './icons/SettingsIcon'
13 +import { SnippetsIcon } from './icons/SnippetsIcon'
7 14 import { UpsellDialog } from './UpsellDialog'
8 -import type { ReactNode } from 'react'
15 +import type { SVGProps } from 'react'
9 16
10 -export const SUBPAGES = ['snippets', 'blueprints', 'cloud-community', 'cloud-library'] as const
17 +const searchParams = new URLSearchParams(window.location.search)
11 18
12 -interface NavLink {
19 +const managePageSlug = window.CODE_SNIPPETS?.urls.manage
20 + ? new URL(window.CODE_SNIPPETS.urls.manage).searchParams.get('page')
21 + : null
22 +
23 +// The manage screen resolves an absent or unrecognised `subpage` to the first subpage, so
24 +// mirror that fallback here — comparing against the raw param would leave every lower-nav
25 +// tab inactive on the default Snippets view, which is where the page opens.
26 +const activeSubpage = searchParams.get('page') === managePageSlug
27 + ? fetchConstQueryParam('subpage', SUBPAGES) ?? SUBPAGES[0]
28 + : null
29 +
30 +interface UpperNavItemProps {
13 31 name: string
14 - url?: string
32 + url: string
15 33 label: string
16 - external?: boolean
17 - icon?: ReactNode
18 - pro?: boolean
19 - pageSlug?: string
20 - subpage?: typeof SUBPAGES[number]
21 - end?: boolean
22 34 }
23 35
24 -const UPPER_NAV_LINKS: readonly NavLink[] = [
25 - {
26 - name: 'docs',
27 - url: 'https://codesnippets.pro/docs',
28 - label: __('Docs', 'code-snippets'),
29 - external: true
30 - },
31 - {
32 - name: 'cloud',
33 - url: 'https://codesnippets.cloud/',
34 - label: __('Cloud Dashboard', 'code-snippets'),
35 - external: true
36 - },
37 - {
38 - name: 'welcome',
39 - url: window.CODE_SNIPPETS?.urls.welcome,
40 - label: __("What's New", 'code-snippets'),
41 - pageSlug: 'code-snippets-welcome'
42 - },
43 - {
44 - name: 'import-snippets',
45 - url: window.CODE_SNIPPETS?.urls.import,
46 - label: _x('Import', 'snippets', 'code-snippets'),
47 - pageSlug: 'import-code-snippets'
48 - }
49 -]
36 +const UpperNavItem = ({ name, url, label }: UpperNavItemProps) => {
37 + const pageSlug = useMemo(() => new URL(url).searchParams.get('page'), [url])
50 38
51 -const LOWER_NAV_LINKS: readonly NavLink[] = [
52 - {
53 - name: 'snippets',
54 - url: window.CODE_SNIPPETS?.urls.manage,
55 - label: __('Snippets', 'code-snippets'),
56 - icon: <SnippetsIcon aria-hidden="true" />,
57 - pageSlug: 'snippets'
58 - },
59 - {
60 - name: 'cloud-community',
61 - label: __('Community Cloud', 'code-snippets'),
62 - icon: <CommunityIcon aria-hidden="true" />,
63 - subpage: 'cloud-community'
64 - },
65 - {
66 - name: 'cloud-library',
67 - label: __('My Library', 'code-snippets'),
68 - icon: <LibraryIcon aria-hidden="true" />,
69 - pro: true,
70 - subpage: 'cloud-library'
71 - },
72 - {
73 - name: 'blueprints',
74 - label: __('Blueprints', 'code-snippets'),
75 - icon: <BlueprintIcon />,
76 - pro: true,
77 - subpage: 'blueprints'
78 - },
79 - {
80 - name: 'settings',
81 - url: window.CODE_SNIPPETS?.urls.settings,
82 - label: __('Settings', 'code-snippets'),
83 - icon: <SettingsIcon aria-hidden="true" />,
84 - pageSlug: 'snippets-settings',
85 - end: true
86 - }
87 -]
39 + const isActive = !searchParams.get('subpage') && searchParams.get('page') === pageSlug
88 40
89 -interface UpperNavProps {
90 - setIsUpsellDialogOpen: (isOpen: boolean) => void
41 + return (
42 + <li>
43 + <a
44 + href={url}
45 + className={classnames(`${name}-link`, { 'active-link': isActive })}
46 + {...!pageSlug && { target: '_blank', rel: 'noopener noreferrer' }}
47 + >
48 + {label}
49 + </a>
50 + </li>
51 + )
91 52 }
53 +const UpperNavItems = () =>
54 + <>
55 + {window.CODE_SNIPPETS?.urls.insights && (
56 + <UpperNavItem
57 + name="insights"
58 + url={window.CODE_SNIPPETS.urls.insights}
59 + label={__('Insights', 'code-snippets')}
60 + />)}
92 61
93 -const UpperNav: React.FC<UpperNavProps> = ({ setIsUpsellDialogOpen }) =>
94 - <div className="code-snippets-toolbar-upper">
95 - <div className="logo">
96 - <img
97 - src={`${window.CODE_SNIPPETS?.urls.plugin}/assets/icon.svg`}
98 - alt={__('Code Snippets logo', 'code-snippets')}
99 - aria-hidden="true"
100 - />
62 + {window.CODE_SNIPPETS?.urls.import && (
63 + <UpperNavItem
64 + name="import-snippets"
65 + url={window.CODE_SNIPPETS.urls.import}
66 + label={_x('Import', 'snippets', 'code-snippets')}
67 + />)}
101 68
102 - <div>{__('Code Snippets', 'code-snippets')}</div>
103 - </div>
69 + {window.CODE_SNIPPETS?.urls.welcome && (
70 + <UpperNavItem
71 + name="welcome"
72 + url={window.CODE_SNIPPETS.urls.welcome}
73 + label={__("What's New", 'code-snippets')}
74 + />)}
104 75
105 - <nav aria-label={__('Main links', 'code-snippets')}>
76 + <UpperNavItem
77 + name="docs"
78 + url="https://codesnippets.pro/docs"
79 + label={__('Docs', 'code-snippets')}
80 + />
81 +
82 + <UpperNavItem
83 + name="cloud"
84 + url={window.CODE_SNIPPETS?.urls.cloud ?? 'https://codesnippets.cloud'}
85 + label={__('Cloud Dashboard', 'code-snippets')}
86 + />
87 +
88 + </>
89 +
90 +const MoreNav = () =>
91 + <li className="toolbar-more-item">
92 + <details className="toolbar-more-menu">
93 + <summary>
94 + {__('More', 'code-snippets')}
95 + <span className="dashicons dashicons-arrow-down-alt2" aria-hidden="true"></span>
96 + </summary>
106 97 <ul>
107 - {UPPER_NAV_LINKS.map(link =>
108 - <li key={link.name}>
109 - <a
110 - href={link.url}
111 - className={classnames(`${link.name}-link`, { 'active-link': isActiveLink(link) })}
112 - {...link.external && { target: '_blank', rel: 'noopener noreferrer' }}
113 - >
114 - {link.label}
115 - </a>
116 - </li>)}
98 + <UpperNavItems />
99 + </ul>
100 + </details>
101 + </li>
117 102
118 - {shouldShowUpsell() && (
119 - <li>
120 - <a
121 - className="button button-large button-secondary"
122 - href="https://codesnippets.pro/pricing/"
123 - target="_blank" rel="noopener noreferrer"
124 - onClick={event => {
125 - event.preventDefault()
126 - setIsUpsellDialogOpen(true)
127 - }}
128 - >
129 - {__('Upgrade to Pro', 'code-snippets')}
130 - </a>
131 - </li>)}
132 - </ul>
133 - </nav>
134 - </div>
103 +const UpperNav: React.FC = () => {
104 + const [isUpsellDialogOpen, setIsUpsellDialogOpen] = useState(false)
135 105
136 -const currentPage = fetchQueryParam('page')
137 -const currentSubpage = fetchQueryParam('subpage')
106 + return (
107 + <div className="code-snippets-toolbar-upper">
108 + <div className="logo">
109 + <img
110 + src={`${window.CODE_SNIPPETS?.urls.plugin}/assets/icon.svg`}
111 + alt={__('Code Snippets logo', 'code-snippets')}
112 + aria-hidden="true"
113 + />
138 114
139 -const isActiveLink = ({ pageSlug, subpage }: Readonly<NavLink>): boolean => {
140 - if (subpage) {
141 - return currentSubpage === subpage
142 - }
143 - if (pageSlug) {
144 - return !currentSubpage && currentPage === pageSlug
145 - }
146 - return false
115 + <div>{__('Code Snippets', 'code-snippets')}</div>
116 + </div>
117 +
118 + <nav aria-label={__('Main links', 'code-snippets')}>
119 + <ul>
120 + <UpperNavItems />
121 + <MoreNav />
122 +
123 + {shouldShowUpsell() && (
124 + <li className="toolbar-upgrade-item">
125 + <a
126 + className="button button-large button-secondary"
127 + href="https://codesnippets.pro/pricing/"
128 + target="_blank" rel="noopener noreferrer"
129 + onClick={event => {
130 + event.preventDefault()
131 + setIsUpsellDialogOpen(true)
132 + }}
133 + >
134 + {__('Upgrade to Pro', 'code-snippets')}
135 + </a>
136 + </li>)}
137 + </ul>
138 + </nav>
139 +
140 + <UpsellDialog isOpen={isUpsellDialogOpen} setIsOpen={setIsUpsellDialogOpen} />
141 + </div>
142 + )
147 143 }
148 144
145 +interface SubpageItemProps {
146 + subpage: SubpageName
147 + label: string
148 + Icon: React.FC<SVGProps<SVGSVGElement>>
149 +}
150 +
151 +const SubpageItem: React.FC<SubpageItemProps> = ({ subpage, label, Icon }) => {
152 + const { demo, isPro } = SUBPAGE_ENTRIES[subpage]
153 + const isNew = 'announce' === demo && !hasSeenDemo(subpage)
154 +
155 + return (
156 + <li>
157 + <a
158 + href={buildUrl(window.CODE_SNIPPETS?.urls.manage, { subpage: subpage })}
159 + className={classnames(`${subpage}-link`, { 'active-link': subpage === activeSubpage })}
160 + >
161 + <Icon aria-hidden="true" />
162 + <span className="toolbar-nav-label">{label}</span>
163 + {demo && (isNew
164 + ? <span className="new-chip">{__('New', 'code-snippets')}</span>
165 + : <span className="demo-chip">{__('Demo', 'code-snippets')}</span>)}
166 + {isPro && !isLicensed() && <span className="pro-chip">{__('Pro', 'code-snippets')}</span>}
167 + </a>
168 + </li>
169 + )
170 +}
171 +
172 +const SettingsItem = () =>
173 + window.CODE_SNIPPETS?.urls.settings
174 + ? <li>
175 + <a
176 + href={window.CODE_SNIPPETS.urls.settings}
177 + className={classnames('settings-link', {
178 + 'active-link': 'snippets-settings' === searchParams.get('page')
179 + })}
180 + >
181 + <SettingsIcon aria-hidden="true" />
182 + <span className="toolbar-nav-label">{__('Settings', 'code-snippets')}</span>
183 + </a>
184 + </li>
185 + : null
186 +
149 187 const LowerNav = () =>
150 188 <div className="code-snippets-toolbar-lower">
151 189 <nav aria-label={__('Main features', 'code-snippets')}>
152 190 <ul>
153 - {LOWER_NAV_LINKS.map(link =>
154 - <li key={link.name} className={link.end ? 'toolbar-end-item' : undefined}>
155 - <a
156 - href={link.url ?? buildUrl(window.CODE_SNIPPETS?.urls.manage, { subpage: link.name })}
157 - className={classnames(`${link.name}-link`, { 'active-link': isActiveLink(link) })}
158 - >
159 - {link.icon}
160 - <span className="toolbar-nav-label">{link.label}</span>
161 - {link.pro && !isLicensed() && <span className="pro-chip">{__('Pro', 'code-snippets')}</span>}
162 - </a>
163 - </li>)}
191 + <SubpageItem
192 + subpage="snippets"
193 + label={__('Snippets', 'code-snippets')}
194 + Icon={SnippetsIcon}
195 + />
196 +
197 + {isLicensed() && (
198 + <SubpageItem
199 + subpage="blueprints"
200 + label={__('Blueprints', 'code-snippets')}
201 + Icon={BlueprintIcon}
202 + />)}
203 +
204 + <SubpageItem
205 + subpage="cloud-community"
206 + label={__('Cloud Community', 'code-snippets')}
207 + Icon={CommunityIcon}
208 + />
209 +
210 + <SubpageItem
211 + subpage="cloud-library"
212 + label={__('Cloud Library', 'code-snippets')}
213 + Icon={LibraryIcon}
214 + />
215 +
216 + {!isLicensed() && (
217 + <SubpageItem
218 + subpage="blueprints"
219 + label={__('Blueprints', 'code-snippets')}
220 + Icon={BlueprintIcon}
221 + />)}
222 +
223 + <SubpageItem
224 + subpage="ai-agent"
225 + label={__('AI Agent', 'code-snippets')}
226 + Icon={AiAgentIcon}
227 + />
228 +
229 + <SettingsItem />
164 230 </ul>
165 231 </nav>
166 232 </div>
167 233
168 -export const Toolbar = () => {
169 - const [isUpsellDialogOpen, setIsUpsellDialogOpen] = useState(false)
170 -
171 - return (
172 - <div className="code-snippets-toolbar">
173 - <UpperNav setIsUpsellDialogOpen={setIsUpsellDialogOpen} />
174 - <LowerNav />
175 - <UpsellDialog isOpen={isUpsellDialogOpen} setIsOpen={setIsUpsellDialogOpen} />
176 - </div>
177 - )
178 -}
234 +export const Toolbar = () =>
235 + <div className="code-snippets-toolbar">
236 + <UpperNav />
237 + <LowerNav />
238 + </div>