@spree/dashboard-core 0.10.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -388
- package/package.json +6 -6
- package/src/components/nav-main.tsx +5 -2
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @spree/dashboard-core
|
|
2
2
|
|
|
3
|
-
Spree Dashboard framework
|
|
3
|
+
Spree Dashboard framework — registries, providers, infra hooks, the admin SDK client singleton, and the `defineDashboardPlugin` extension API.
|
|
4
4
|
|
|
5
|
-
This is the package plugin authors install. Use it to register nav entries, slot widgets, table column extensions, and settings pages from a separate npm package that
|
|
5
|
+
This is the package plugin authors install. Use it to register nav entries, slot widgets, table column extensions, form fields, and settings pages from a separate npm package that `@spree/dashboard` activates at boot.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -10,409 +10,42 @@ This is the package plugin authors install. Use it to register nav entries, slot
|
|
|
10
10
|
pnpm add @spree/dashboard-core @spree/dashboard-ui
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
**List `@spree/dashboard-core` (and React, the TanStack packages, i18next, …) as peer dependencies in your plugin.** The registries are module singletons — a bundled second copy would write to Maps the app never reads, a silent no-op. The [plugin distribution guide](https://spreecommerce.org/docs/developer/dashboard/plugins/distributing) covers the full peer list.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
## What's in the box
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
@spree/dashboard-core
|
|
21
|
-
├── adminClient # @spree/admin-sdk instance, baseUrl wired to VITE_SPREE_API_URL
|
|
22
|
-
│
|
|
23
|
-
├── Providers (mount in your app shell, in this order)
|
|
24
|
-
│ ├── AuthProvider # JWT + refresh token, /auth/refresh on cold load
|
|
25
|
-
│ ├── PermissionProvider # /api/v3/admin/me → CanCanCan rules
|
|
26
|
-
│ └── StoreProvider # current store context (multi-store)
|
|
27
|
-
│
|
|
28
|
-
├── Registries (the four extension points)
|
|
29
|
-
│ ├── nav # sidebar items
|
|
30
|
-
│ ├── settingsNav # settings sub-shell groups + items
|
|
31
|
-
│ ├── slot # named injection points (`product.form_sidebar`, ...)
|
|
32
|
-
│ └── tables # column add/remove/update per list view
|
|
33
|
-
│
|
|
34
|
-
├── Infra hooks
|
|
35
|
-
│ ├── useAuth # { token, user, login(), logout() }
|
|
36
|
-
│ ├── usePermissions # CanCanCan rules + <Can> helper
|
|
37
|
-
│ ├── useStore # current store
|
|
38
|
-
│ ├── useResourceMutation # useMutation + toast + form-error mapping
|
|
39
|
-
│ ├── useDirectUpload # Active Storage direct-upload
|
|
40
|
-
│ ├── useGlobalSearch # cross-resource search
|
|
41
|
-
│ ├── useCommandPalette # ⌘K palette state
|
|
42
|
-
│ ├── useCopyToClipboard
|
|
43
|
-
│ ├── useScrolled
|
|
44
|
-
│ ├── useCountries # admin/countries (geo data, cached)
|
|
45
|
-
│ ├── useExport # CSV export jobs (polled)
|
|
46
|
-
│ └── useCustomFields # metafield definitions + values
|
|
47
|
-
│
|
|
48
|
-
├── i18n (i18next + react-i18next)
|
|
49
|
-
│ ├── Base translation namespace (`admin.actions.*`, `admin.common.*`, `admin.validation.*`, …)
|
|
50
|
-
│ ├── Trans, useTranslation # re-exported from react-i18next
|
|
51
|
-
│ └── i18n # the i18next singleton (call addResourceBundle to extend)
|
|
52
|
-
│
|
|
53
|
-
├── Helpers
|
|
54
|
-
│ ├── filtersToRansack # UI filter shape → Ransack params
|
|
55
|
-
│ ├── mapSpreeErrorsToForm # 422 → RHF setError
|
|
56
|
-
│ ├── formatPrice / getInitials
|
|
57
|
-
│ ├── Subject, Action # CanCanCan subject/action constants
|
|
58
|
-
│ └── queryClient # TanStack Query client singleton
|
|
59
|
-
│
|
|
60
|
-
└── defineDashboardPlugin # the one-call extension facade
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Additional entry point:
|
|
64
|
-
|
|
65
|
-
- `@spree/dashboard-core/vite` — Vite plugin that wires Tailwind v4. See [Vite integration](#vite-integration).
|
|
66
|
-
|
|
67
|
-
## Vite integration
|
|
68
|
-
|
|
69
|
-
The dashboard relies on Tailwind v4, which doesn't scan `node_modules` by default and only accepts filesystem paths in `@source` directives (not bare package specifiers). The `spreeDashboardPlugin` from `@spree/dashboard-core/vite` resolves each Spree dashboard package and each host-named plugin through Node module resolution and injects matching `@source` directives into your CSS entry at build time. It also bundles `@tailwindcss/vite` itself so plugin ordering is guaranteed — hosts must NOT register `@tailwindcss/vite` separately.
|
|
70
|
-
|
|
71
|
-
### Using the full `@spree/dashboard` app shell
|
|
72
|
-
|
|
73
|
-
```ts
|
|
74
|
-
// host vite.config.ts
|
|
75
|
-
import { spreeDashboardPlugin } from '@spree/dashboard-core/vite'
|
|
76
|
-
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
|
|
77
|
-
import react from '@vitejs/plugin-react'
|
|
78
|
-
import { defineConfig } from 'vite'
|
|
79
|
-
|
|
80
|
-
export default defineConfig({
|
|
81
|
-
plugins: [
|
|
82
|
-
spreeDashboardPlugin({
|
|
83
|
-
plugins: [
|
|
84
|
-
'@my-store/orders-dashboard-plugin',
|
|
85
|
-
'@my-store/wishlists-dashboard-plugin',
|
|
86
|
-
],
|
|
87
|
-
}),
|
|
88
|
-
TanStackRouterVite(),
|
|
89
|
-
react(),
|
|
90
|
-
],
|
|
91
|
-
})
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
```css
|
|
95
|
-
/* host src/styles.css */
|
|
96
|
-
@import "@spree/dashboard/styles.css";
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### Building a custom dashboard on `@spree/dashboard-core` + `@spree/dashboard-ui`
|
|
100
|
-
|
|
101
|
-
If you're not using `@spree/dashboard`'s app shell — e.g. a vendor portal or B2B buyer admin built directly on the registries and primitives — point `cssEntry` at your own CSS file:
|
|
102
|
-
|
|
103
|
-
```ts
|
|
104
|
-
// custom-dashboard vite.config.ts
|
|
105
|
-
import { spreeDashboardPlugin } from '@spree/dashboard-core/vite'
|
|
106
|
-
|
|
107
|
-
export default defineConfig({
|
|
108
|
-
plugins: [
|
|
109
|
-
spreeDashboardPlugin({
|
|
110
|
-
cssEntry: './src/admin.css',
|
|
111
|
-
plugins: ['@my-store/vendor-portal-plugin'],
|
|
112
|
-
}),
|
|
113
|
-
// host owns react, router, etc.
|
|
114
|
-
],
|
|
115
|
-
})
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
```css
|
|
119
|
-
/* custom-dashboard src/admin.css */
|
|
120
|
-
@import "@spree/dashboard-ui/styles.css";
|
|
121
|
-
/* Tailwind auto-scans the host's own src/, so host-authored classes
|
|
122
|
-
work without configuration. The Vite plugin injects @source for
|
|
123
|
-
@spree/dashboard-core, @spree/dashboard-ui, and any named plugins. */
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### `@spree/dashboard-ui` only (no `@spree/dashboard-core`)
|
|
127
|
-
|
|
128
|
-
If you only need design-system primitives and tokens, no registries, no providers — you don't need this Vite plugin at all. Install `@spree/dashboard-ui`, set up `@tailwindcss/vite` yourself, and import its stylesheet:
|
|
129
|
-
|
|
130
|
-
```css
|
|
131
|
-
@import "@spree/dashboard-ui/styles.css";
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
`dashboard-ui/styles.css` carries its own `@source` directives covering its components.
|
|
135
|
-
|
|
136
|
-
### Options
|
|
15
|
+
## Quick start
|
|
137
16
|
|
|
138
17
|
```ts
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
* The plugin injects @source directives into this file. Defaults to
|
|
143
|
-
* `./src/styles.css`.
|
|
144
|
-
*/
|
|
145
|
-
cssEntry: './src/styles.css',
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Names of installed dashboard plugin packages. Each must be a resolvable
|
|
149
|
-
* npm specifier. The plugin resolves each via Node module resolution and
|
|
150
|
-
* tells Tailwind to scan its source files.
|
|
151
|
-
*/
|
|
152
|
-
plugins: ['@my-store/orders-plugin'],
|
|
153
|
-
})
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### What plugin authors must do
|
|
157
|
-
|
|
158
|
-
Ship `src/` in their published package so Tailwind has source files to scan:
|
|
159
|
-
|
|
160
|
-
```json
|
|
161
|
-
// plugin's package.json
|
|
162
|
-
{
|
|
163
|
-
"name": "@my-store/orders-dashboard-plugin",
|
|
164
|
-
"main": "./src/index.ts",
|
|
165
|
-
"types": "./src/index.ts",
|
|
166
|
-
"files": ["src", "README.md"]
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
Plugin authors do NOT need to ship pre-built CSS, configure Tailwind, or care about the host's package manager — adding the plugin name to the host's `plugins: [...]` is enough.
|
|
171
|
-
|
|
172
|
-
## Plugin authoring — the basics
|
|
173
|
-
|
|
174
|
-
Your plugin is an npm package. Its entry module calls `defineDashboardPlugin({...})` at import time. The dashboard app imports your entry module before first render — every registry handles late registration via `useSyncExternalStore`, so even if your plugin loads asynchronously the sidebar/slots re-render.
|
|
175
|
-
|
|
176
|
-
```tsx
|
|
177
|
-
// my-spree-plugin/src/index.ts
|
|
178
|
-
import { defineDashboardPlugin, usePermissions } from '@spree/dashboard-core'
|
|
179
|
-
import { Card, CardHeader, CardTitle, CardContent } from '@spree/dashboard-ui'
|
|
180
|
-
import { BarChartIcon } from 'lucide-react'
|
|
181
|
-
|
|
182
|
-
function WishlistsWidget({ product }: { product: { id: string; wishlist_count: number } }) {
|
|
183
|
-
return (
|
|
184
|
-
<Card>
|
|
185
|
-
<CardHeader><CardTitle>Wishlists</CardTitle></CardHeader>
|
|
186
|
-
<CardContent>{product.wishlist_count} customers</CardContent>
|
|
187
|
-
</Card>
|
|
188
|
-
)
|
|
189
|
-
}
|
|
18
|
+
import { defineDashboardPlugin } from '@spree/dashboard-core/plugin'
|
|
19
|
+
import { PackageIcon } from 'lucide-react'
|
|
20
|
+
import { WishlistCard } from './wishlist-card'
|
|
190
21
|
|
|
191
22
|
defineDashboardPlugin({
|
|
192
|
-
nav: [
|
|
193
|
-
{ key: 'wishlists', label: 'Wishlists', path: '/wishlists', icon: BarChartIcon, position: 50 },
|
|
194
|
-
],
|
|
23
|
+
nav: [{ key: 'wishlists', label: 'Wishlists', path: '/wishlists', icon: PackageIcon }],
|
|
195
24
|
slots: {
|
|
196
|
-
'product.form_sidebar': [
|
|
197
|
-
{ id: 'wishlist-count', component: WishlistsWidget, position: 50 },
|
|
198
|
-
],
|
|
25
|
+
'product.form_sidebar': [{ id: 'wishlist-count', component: WishlistCard }],
|
|
199
26
|
},
|
|
200
27
|
tables: {
|
|
201
28
|
products: {
|
|
202
|
-
add: [{ key: 'wishlist_count', label: 'Wishlists',
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
})
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
Then in the consuming app's entry point:
|
|
209
|
-
|
|
210
|
-
```ts
|
|
211
|
-
// my-spree-app/src/main.tsx
|
|
212
|
-
import '@spree/dashboard-core/lib/i18n' // base translations
|
|
213
|
-
import 'my-spree-plugin' // your plugin registers extensions
|
|
214
|
-
// ...rest of normal dashboard bootstrap
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
## Translations — extending the base namespace
|
|
218
|
-
|
|
219
|
-
Plugin authors **inherit the framework's base translation namespace** (`admin.actions.save`, `admin.common.no_results`, `admin.validation.required`, `admin.fields.<simple>.label`, …) for free. To add your own keys, call `i18n.addResourceBundle` after the side-effect import.
|
|
220
|
-
|
|
221
|
-
```ts
|
|
222
|
-
// my-spree-plugin/src/index.ts
|
|
223
|
-
import { i18n } from '@spree/dashboard-core'
|
|
224
|
-
import en from './locales/en.json'
|
|
225
|
-
//
|
|
226
|
-
// {
|
|
227
|
-
// "admin": {
|
|
228
|
-
// "wishlists": {
|
|
229
|
-
// "title": "Wishlists",
|
|
230
|
-
// "empty_state": "No wishlists yet"
|
|
231
|
-
// }
|
|
232
|
-
// }
|
|
233
|
-
// }
|
|
234
|
-
|
|
235
|
-
i18n.addResourceBundle('en', 'translation', en, true, true)
|
|
236
|
-
// `deep: true` + `overwrite: true` merge nested keys into the base namespace
|
|
237
|
-
// without dropping anything the framework provided.
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
Now anywhere in your plugin you can mix base keys and your own:
|
|
241
|
-
|
|
242
|
-
```tsx
|
|
243
|
-
import { useTranslation } from '@spree/dashboard-core'
|
|
244
|
-
|
|
245
|
-
function WishlistsHeader() {
|
|
246
|
-
const { t } = useTranslation()
|
|
247
|
-
return (
|
|
248
|
-
<header>
|
|
249
|
-
<h1>{t('admin.wishlists.title')}</h1>
|
|
250
|
-
<button>{t('admin.actions.create')}</button>{/* base */}
|
|
251
|
-
</header>
|
|
252
|
-
)
|
|
253
|
-
}
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
### When plugins should pick their own sub-namespace
|
|
257
|
-
|
|
258
|
-
`addResourceBundle` deep-merges into `admin.*`, so `admin.actions.save` from a plugin would **overwrite** the framework's `admin.actions.save`. To avoid collisions, put plugin-specific text under a vendor-prefixed key:
|
|
259
|
-
|
|
260
|
-
```jsonc
|
|
261
|
-
{
|
|
262
|
-
"admin": {
|
|
263
|
-
"wishlists": { ... } // ok — fresh namespace under admin
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
Not:
|
|
269
|
-
|
|
270
|
-
```jsonc
|
|
271
|
-
{
|
|
272
|
-
"admin": {
|
|
273
|
-
"actions": {
|
|
274
|
-
"share": "Share" // collides if the framework ever adds `admin.actions.share`
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
Prefer `admin.<plugin>.*` for everything you own. Use base keys (`admin.actions.*`, `admin.common.*`, `admin.fields.<simple>.*`) by *reading* them, not by extending them.
|
|
281
|
-
|
|
282
|
-
### Multi-locale plugins
|
|
283
|
-
|
|
284
|
-
Same pattern, once per locale:
|
|
285
|
-
|
|
286
|
-
```ts
|
|
287
|
-
import { i18n } from '@spree/dashboard-core'
|
|
288
|
-
import en from './locales/en.json'
|
|
289
|
-
import fr from './locales/fr.json'
|
|
290
|
-
|
|
291
|
-
i18n.addResourceBundle('en', 'translation', en, true, true)
|
|
292
|
-
i18n.addResourceBundle('fr', 'translation', fr, true, true)
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
The framework currently ships English only; once Spree's locale list grows the same `addResourceBundle` pattern applies.
|
|
296
|
-
|
|
297
|
-
## Extension surface — the registries
|
|
298
|
-
|
|
299
|
-
### `nav` — main sidebar items
|
|
300
|
-
|
|
301
|
-
```ts
|
|
302
|
-
import { nav } from '@spree/dashboard-core'
|
|
303
|
-
import { PackageIcon } from 'lucide-react'
|
|
304
|
-
|
|
305
|
-
nav.add({
|
|
306
|
-
key: 'wishlists',
|
|
307
|
-
label: 'Wishlists',
|
|
308
|
-
path: '/wishlists', // prefixed with /$storeId at render time
|
|
309
|
-
icon: PackageIcon,
|
|
310
|
-
position: 50, // built-ins use 100/200/300 — slot in between
|
|
311
|
-
subject: 'Spree::Wishlist', // CanCanCan subject — hides item without permission
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
// Or: insert relative to an existing entry
|
|
315
|
-
nav.insertAfter('orders', { key: 'wishlists', label: 'Wishlists', path: '/wishlists' })
|
|
316
|
-
nav.update('orders', { label: 'All Orders' })
|
|
317
|
-
nav.remove('legacy-thing')
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
### `settingsNav` — settings sub-shell
|
|
321
|
-
|
|
322
|
-
Settings entries cluster under groups:
|
|
323
|
-
|
|
324
|
-
```ts
|
|
325
|
-
import { settingsNav } from '@spree/dashboard-core'
|
|
326
|
-
|
|
327
|
-
settingsNav.addGroup({ key: 'integrations', label: 'Integrations', position: 500 })
|
|
328
|
-
settingsNav.add({
|
|
329
|
-
key: 'wishlist-settings',
|
|
330
|
-
label: 'Wishlists',
|
|
331
|
-
path: '/wishlists', // prefixed with /$storeId/settings at render time
|
|
332
|
-
group: 'integrations',
|
|
333
|
-
position: 100,
|
|
334
|
-
})
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
### `registerSlot` — named injection points
|
|
338
|
-
|
|
339
|
-
Slots are typed by their context. A `product.form_sidebar` slot receives the product, ambient permissions, store, and user:
|
|
340
|
-
|
|
341
|
-
```tsx
|
|
342
|
-
import { registerSlot } from '@spree/dashboard-core'
|
|
343
|
-
import { Card } from '@spree/dashboard-ui'
|
|
344
|
-
|
|
345
|
-
registerSlot<{ product: { id: string; wishlist_count: number } }>('product.form_sidebar', {
|
|
346
|
-
id: 'wishlist-count',
|
|
347
|
-
component: ({ product, permissions }) => {
|
|
348
|
-
if (!permissions?.can('manage', 'Spree::Wishlist')) return null
|
|
349
|
-
return <Card>Wishlists: {product.wishlist_count}</Card>
|
|
350
|
-
},
|
|
351
|
-
position: 50,
|
|
352
|
-
// Optional visibility predicate — receives the merged context
|
|
353
|
-
if: (ctx) => ctx.product.wishlist_count > 0,
|
|
354
|
-
})
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
Built-in slot names follow a `resource.location` convention (e.g. `product.form_sidebar`, `order.page_body`, `product.actions`). The full list lives in the [slots catalog](https://spreecommerce.org/docs/developer/dashboard/slots-catalog).
|
|
358
|
-
|
|
359
|
-
### `tables` — list-view column extensions
|
|
360
|
-
|
|
361
|
-
```ts
|
|
362
|
-
import { tables } from '@spree/dashboard-core'
|
|
363
|
-
|
|
364
|
-
tables.products.addColumn({
|
|
365
|
-
key: 'wishlist_count',
|
|
366
|
-
label: 'Wishlists',
|
|
367
|
-
sortable: true,
|
|
368
|
-
render: (product) => product.wishlist_count,
|
|
369
|
-
})
|
|
370
|
-
tables.products.removeColumn('legacy_column')
|
|
371
|
-
tables.products.updateColumn('total', { label: 'Net total' })
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
**Ordering:** built-in tables register lazily via side-effect imports in route files (`import '@/tables/products'` inside the products route), so calling `tables.products.addColumn(...)` from your plugin's entry module — which loads before any route mounts — would otherwise race the registration. The mutator handles this transparently: mutations on a not-yet-registered table are queued and replayed when `defineTable('products', ...)` runs. Mutations on tables that never register stay pending and never fire, which is the right behavior when your plugin extends an optional feature.
|
|
375
|
-
|
|
376
|
-
## `defineDashboardPlugin` — one-call facade
|
|
377
|
-
|
|
378
|
-
Every registry in a single declarative config:
|
|
379
|
-
|
|
380
|
-
```ts
|
|
381
|
-
import { defineDashboardPlugin } from '@spree/dashboard-core/plugin'
|
|
382
|
-
|
|
383
|
-
defineDashboardPlugin({
|
|
384
|
-
nav: [...],
|
|
385
|
-
settingsNavGroups: [...],
|
|
386
|
-
settingsNav: [...],
|
|
387
|
-
slots: { 'product.form_sidebar': [...] },
|
|
388
|
-
tables: {
|
|
389
|
-
products: {
|
|
390
|
-
add: [...],
|
|
29
|
+
add: [{ key: 'wishlist_count', label: 'Wishlists', render: (p) => p.wishlist_count }],
|
|
391
30
|
remove: ['legacy_column'],
|
|
392
|
-
update: { total: { label: 'Net total' } },
|
|
393
31
|
},
|
|
394
32
|
},
|
|
395
|
-
|
|
396
|
-
// persist through the form's own Save. Render the input from a slot widget
|
|
397
|
-
// bound via useHostForm().
|
|
398
|
-
formFields: {
|
|
399
|
-
product: [{ name: 'tech_specs', from: (product) => product?.tech_specs ?? '' }],
|
|
400
|
-
},
|
|
401
|
-
// Replace the input widget for a specific custom field definition.
|
|
402
|
-
customFieldComponents: { 'specs.color': ColorPicker },
|
|
33
|
+
formFields: { product: [{ name: 'tech_specs', from: (p) => p?.tech_specs ?? '' }] },
|
|
403
34
|
})
|
|
404
35
|
```
|
|
405
36
|
|
|
406
|
-
|
|
37
|
+
A second entry point, `@spree/dashboard-core/vite`, provides the Vite plugin that wires Tailwind v4 and plugin auto-discovery for custom hosts.
|
|
407
38
|
|
|
408
|
-
##
|
|
39
|
+
## Documentation
|
|
409
40
|
|
|
410
|
-
|
|
41
|
+
The full guides live on the docs site — this README intentionally stays short:
|
|
411
42
|
|
|
412
|
-
|
|
43
|
+
- [Dashboard overview](https://spreecommerce.org/docs/developer/dashboard/overview) & [concepts](https://spreecommerce.org/docs/developer/dashboard/concepts)
|
|
44
|
+
- [Customization quickstart](https://spreecommerce.org/docs/developer/dashboard/customization/quickstart) — nav, routes, slots, tables, translations, permissions
|
|
45
|
+
- [Slots catalog](https://spreecommerce.org/docs/developer/dashboard/slots-catalog) — every named injection point
|
|
46
|
+
- [Plugin authoring](https://spreecommerce.org/docs/developer/dashboard/plugins/overview) — scaffolding, publishing, distributing
|
|
47
|
+
- [Public API](https://spreecommerce.org/docs/developer/dashboard/public-api) — everything this package exports, with stability guarantees
|
|
413
48
|
|
|
414
|
-
##
|
|
49
|
+
## License
|
|
415
50
|
|
|
416
|
-
|
|
417
|
-
- **i18n bootstrap**: `import '@spree/dashboard-core/lib/i18n'` initializes i18next with the base namespace. Must run before any component calls `useTranslation`. The dashboard's `i18n-setup.ts` does this + adds the app's resources via `addResourceBundle`. Plugins follow the same pattern.
|
|
418
|
-
- **Registries are module singletons**: see "Peer dependencies" above. Build your plugin with `@spree/dashboard-core` listed as a peer dep, not a regular dep.
|
|
51
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spree/dashboard-core",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.12.0",
|
|
5
5
|
"description": "Spree Dashboard framework — registries (table, nav, slot, settings-nav), providers (auth, permission, store), admin SDK client, generic infra hooks, and the defineDashboardPlugin extension API. Developer Preview.",
|
|
6
6
|
"author": "Vendo Connect Inc.",
|
|
7
7
|
"homepage": "https://spreecommerce.org",
|
|
@@ -48,6 +48,8 @@
|
|
|
48
48
|
],
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@hookform/resolvers": "^5.2.2",
|
|
51
|
+
"@spree/admin-sdk": ">=0.7.0 <2",
|
|
52
|
+
"@spree/dashboard-ui": ">=0.10.0 <2",
|
|
51
53
|
"@tanstack/react-hotkeys": "^0.10.0",
|
|
52
54
|
"@tanstack/react-query": "^5.100.9",
|
|
53
55
|
"@tanstack/react-router": "^1.169.2",
|
|
@@ -56,9 +58,7 @@
|
|
|
56
58
|
"react-dom": "^19.2.6",
|
|
57
59
|
"react-hook-form": "^7.75.0",
|
|
58
60
|
"react-i18next": "^17.0.8",
|
|
59
|
-
"sonner": "^2.0.7"
|
|
60
|
-
"@spree/admin-sdk": "^0.6.0",
|
|
61
|
-
"@spree/dashboard-ui": "^0.10.0"
|
|
61
|
+
"sonner": "^2.0.7"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@dnd-kit/core": "^6.3.1",
|
|
@@ -79,8 +79,8 @@
|
|
|
79
79
|
"typescript": "~6.0.3",
|
|
80
80
|
"vite": "^8.0.16",
|
|
81
81
|
"vitest": "^4.1.5",
|
|
82
|
-
"@spree/admin-sdk": "^0.
|
|
83
|
-
"@spree/dashboard-ui": "^0.
|
|
82
|
+
"@spree/admin-sdk": "^0.7.0",
|
|
83
|
+
"@spree/dashboard-ui": "^0.12.0"
|
|
84
84
|
},
|
|
85
85
|
"scripts": {
|
|
86
86
|
"lint": "biome check src",
|
|
@@ -102,9 +102,12 @@ function NavItemContent({
|
|
|
102
102
|
>
|
|
103
103
|
<Link to={item.url}>
|
|
104
104
|
<NavIcon icon={item.icon} isActive={itemIsActive} />
|
|
105
|
-
|
|
105
|
+
{/* Explicitly hide in collapsed icon mode: a trailing badge span would
|
|
106
|
+
otherwise steal the `span:last-child` position the base button style
|
|
107
|
+
relies on to hide the label, leaving the title visible. */}
|
|
108
|
+
<span className="group-data-[collapsible=icon]:hidden">{item.title}</span>
|
|
106
109
|
{item.badge && (
|
|
107
|
-
<span className="ml-auto">
|
|
110
|
+
<span className="ml-auto group-data-[collapsible=icon]:hidden">
|
|
108
111
|
<item.badge />
|
|
109
112
|
</span>
|
|
110
113
|
)}
|