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 +211 -141 4.0.0-beta.1 → trunk View file →
@@ -1,168 +1,238 @@
1 -import { __ } from '@wordpress/i18n'
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 { 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 -interface NavLink {
17 +const searchParams = new URLSearchParams(window.location.search)
18 +
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 {
11 31 name: string
12 - url?: string
32 + url: string
13 33 label: string
14 - external?: boolean
15 - icon?: ReactNode
16 - pro?: boolean
17 - pageSlug?: string
18 - subpage?: string
19 34 }
20 35
21 -const UPPER_NAV_LINKS: NavLink[] = [
22 - {
23 - name: 'docs',
24 - url: 'https://codesnippets.pro/docs',
25 - label: __('Docs', 'code-snippets'),
26 - external: true
27 - },
28 - {
29 - name: 'cloud',
30 - url: 'https://codesnippets.cloud/',
31 - label: __('Cloud Dashboard', 'code-snippets'),
32 - external: true
33 - },
34 - {
35 - name: 'welcome',
36 - url: window.CODE_SNIPPETS?.urls.welcome,
37 - label: __("What's New", 'code-snippets'),
38 - pageSlug: 'code-snippets-welcome'
39 - }
40 -]
36 +const UpperNavItem = ({ name, url, label }: UpperNavItemProps) => {
37 + const pageSlug = useMemo(() => new URL(url).searchParams.get('page'), [url])
41 38
42 -const LOWER_NAV_LINKS: NavLink[] = [
43 - {
44 - name: 'snippets',
45 - url: window.CODE_SNIPPETS?.urls.manage,
46 - label: __('Snippets', 'code-snippets'),
47 - icon: <SnippetsIcon aria-hidden="true" />,
48 - pageSlug: 'snippets'
49 - },
50 - {
51 - name: 'cloud-community',
52 - label: __('Community Cloud', 'code-snippets'),
53 - icon: <CommunityIcon aria-hidden="true" />,
54 - subpage: 'cloud-community'
55 - },
56 - {
57 - name: 'cloud-library',
58 - label: __('My Library', 'code-snippets'),
59 - icon: <LibraryIcon aria-hidden="true" />,
60 - pro: true,
61 - subpage: 'cloud-library'
62 - },
63 - {
64 - name: 'settings',
65 - url: window.CODE_SNIPPETS?.urls.settings,
66 - label: __('Settings', 'code-snippets'),
67 - icon: <SettingsIcon aria-hidden="true" />,
68 - pageSlug: 'snippets-settings'
69 - }
70 -]
39 + const isActive = !searchParams.get('subpage') && searchParams.get('page') === pageSlug
71 40
72 -interface NavProps {
73 - 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 + )
74 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 + />)}
75 61
76 -const UpperNav: React.FC<NavProps> = ({ setIsUpsellDialogOpen }) =>
77 - <div className="code-snippets-toolbar-upper">
78 - <div className="logo">
79 - <img
80 - src={`${window.CODE_SNIPPETS?.urls.plugin}/assets/icon.svg`}
81 - alt={__('Code Snippets logo', 'code-snippets')}
82 - aria-hidden="true"
83 - />
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 + />)}
84 68
85 - <h1>{__('Code Snippets', 'code-snippets')}</h1>
86 - </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 + />)}
87 75
88 - <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>
89 97 <ul>
90 - {UPPER_NAV_LINKS.map(link =>
91 - <li key={link.name}>
92 - <a
93 - href={link.url}
94 - className={classnames(`${link.name}-link`, { 'active-link': isActiveLink(link) })}
95 - {...link.external && { target: '_blank', rel: 'noopener noreferrer' }}
96 - >
97 - {link.label}
98 - </a>
99 - </li>
100 - )}
101 - {shouldShowUpsell()
102 - ? <li>
103 - <a
104 - className="button button-large button-secondary"
105 - href="https://codesnippets.pro/pricing/"
106 - target="_blank" rel="noopener noreferrer"
107 - onClick={event => {
108 - event.preventDefault()
109 - setIsUpsellDialogOpen(true)
110 - }}
111 - >
112 - {__('Upgrade to Pro', 'code-snippets')}
113 - </a>
114 - </li>
115 - : null}
98 + <UpperNavItems />
116 99 </ul>
117 - </nav>
118 - </div>
100 + </details>
101 + </li>
119 102
120 -const currentPage = fetchQueryParam('page')
121 -const currentSubpage = fetchQueryParam('subpage')
103 +const UpperNav: React.FC = () => {
104 + const [isUpsellDialogOpen, setIsUpsellDialogOpen] = useState(false)
122 105
123 -const isActiveLink = ({ pageSlug, subpage }: NavLink): boolean => {
124 - if (subpage) {
125 - return currentSubpage === subpage
126 - }
127 - if (pageSlug) {
128 - return !currentSubpage && currentPage === pageSlug
129 - }
130 - return false
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 + />
114 +
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 + )
131 143 }
132 144
133 -const LowerNav: React.FC<NavProps> = ({ setIsUpsellDialogOpen }) =>
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 +
187 +const LowerNav = () =>
134 188 <div className="code-snippets-toolbar-lower">
135 189 <nav aria-label={__('Main features', 'code-snippets')}>
136 190 <ul>
137 - {LOWER_NAV_LINKS.map(link =>
138 - <li key={link.name}>
139 - <a
140 - href={link.url ?? buildUrl(window.CODE_SNIPPETS?.urls.manage, { subpage: link.name })}
141 - className={classnames(`${link.name}-link`, { 'active-link': isActiveLink(link) })}
142 - onClick={event => {
143 - if (link.pro && !isLicensed()) {
144 - event.preventDefault()
145 - setIsUpsellDialogOpen(true)
146 - }
147 - }}
148 - >
149 - {link.icon}
150 - <span>{link.label}</span>
151 - {link.pro && !isLicensed() && <span className="pro-chip">{__('Pro', 'code-snippets')}</span>}
152 - </a>
153 - </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 />
154 230 </ul>
155 231 </nav>
156 232 </div>
157 233
158 -export const Toolbar = () => {
159 - const [isUpsellDialogOpen, setIsUpsellDialogOpen] = useState(false)
160 -
161 - return (
162 - <div className="code-snippets-toolbar">
163 - <UpperNav setIsUpsellDialogOpen={setIsUpsellDialogOpen} />
164 - <LowerNav setIsUpsellDialogOpen={setIsUpsellDialogOpen} />
165 - <UpsellDialog isOpen={isUpsellDialogOpen} setIsOpen={setIsUpsellDialogOpen} />
166 - </div>
167 - )
168 -}
234 +export const Toolbar = () =>
235 + <div className="code-snippets-toolbar">
236 + <UpperNav />
237 + <LowerNav />
238 + </div>