@nocobase/app-template-default 1.0.0-beta.67 → 1.0.0-beta.69

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.
Files changed (87) hide show
  1. package/.agents/rules/components.md +2 -2
  2. package/.agents/rules/shell.md +5 -1
  3. package/AGENTS.md +5 -3
  4. package/Dockerfile.dockerignore +0 -1
  5. package/README.MD +3 -1
  6. package/client/app.ts +15 -0
  7. package/client/components/data-table/column-header.tsx +91 -0
  8. package/client/components/data-table/data-table.tsx +216 -0
  9. package/client/components/data-table/index.ts +13 -0
  10. package/client/components/data-table/pagination.tsx +165 -0
  11. package/client/components/data-table/view-options.tsx +77 -0
  12. package/client/components/page-header.tsx +1 -1
  13. package/client/components/permission-editor.tsx +589 -0
  14. package/client/components/permission-filter.tsx +455 -0
  15. package/client/components/route-overlay.tsx +26 -13
  16. package/client/components/status-badge.tsx +7 -1
  17. package/client/components/ui/checkbox.tsx +25 -0
  18. package/client/components/ui/sidebar.tsx +1 -4
  19. package/client/components/ui/switch.tsx +30 -0
  20. package/client/components/ui/table.tsx +113 -0
  21. package/client/extensions/nocobase-inbox/inbox-item.tsx +44 -58
  22. package/client/extensions/nocobase-inbox/inbox-list.tsx +73 -75
  23. package/client/index.tsx +4 -0
  24. package/client/layouts/components/app-sidebar.tsx +6 -6
  25. package/client/layouts/components/layout-header.tsx +1 -1
  26. package/client/layouts/components/navigation-menu.tsx +223 -12
  27. package/client/layouts/components/sidebar-footer.tsx +1 -1
  28. package/client/layouts/components/user-menu.tsx +1 -1
  29. package/client/layouts/use-sidebar-preference.ts +62 -42
  30. package/client/lib/navigation-transform.ts +8 -0
  31. package/client/lib/scrollbar.ts +88 -0
  32. package/client/locales/system/en-US.ts +11 -0
  33. package/client/locales/system/settings-en-US.ts +141 -0
  34. package/client/locales/system/settings-zh-CN.ts +135 -0
  35. package/client/locales/system/zh-CN.ts +22 -12
  36. package/client/pages/auth/auth-page.css +52 -0
  37. package/client/pages/auth/login.tsx +1 -1
  38. package/client/pages/auth/reset-password.tsx +57 -14
  39. package/client/pages/auth/shared.tsx +26 -24
  40. package/client/pages/home/index.tsx +2 -2
  41. package/client/pages/settings/hooks.tsx +137 -0
  42. package/client/pages/settings/permission-filters.tsx +119 -0
  43. package/client/pages/settings/permissions-model.ts +260 -0
  44. package/client/pages/settings/role-form.tsx +426 -0
  45. package/client/pages/settings/roles.tsx +163 -0
  46. package/client/pages/settings/routes.ts +79 -0
  47. package/client/pages/settings/shared.tsx +119 -0
  48. package/client/pages/settings/user-detail.tsx +268 -0
  49. package/client/pages/settings/user-form.tsx +427 -0
  50. package/client/pages/settings/users.tsx +451 -0
  51. package/client/routing/app-router.tsx +4 -1
  52. package/client/routing/route-navigation.ts +10 -1
  53. package/client/routing/session-preparation.tsx +65 -0
  54. package/client/styles.css +257 -24
  55. package/client/theme/theme-settings.tsx +12 -7
  56. package/client/theme/themes/compact.css +9 -14
  57. package/client/theme/themes/default.css +7 -12
  58. package/package.json +26 -25
  59. package/server/app.ts +2 -0
  60. package/server/config/notification.ts +4 -7
  61. package/tests/components/app-layout-routes.test.tsx +14 -7
  62. package/tests/components/auth-i18n.test.tsx +57 -5
  63. package/tests/components/data-table.test.tsx +127 -0
  64. package/tests/components/layout-i18n.test.tsx +10 -3
  65. package/tests/components/navigation-menu.test.tsx +155 -16
  66. package/tests/components/permission-editor.test.tsx +477 -0
  67. package/tests/components/permission-filter-binding.test.tsx +150 -0
  68. package/tests/components/permission-filter.test.tsx +202 -0
  69. package/tests/components/settings.test.tsx +294 -0
  70. package/tests/components/sidebar-permissions.test.tsx +17 -19
  71. package/tests/components/sidebar-preference.test.tsx +209 -14
  72. package/tests/components/status-badge.test.tsx +22 -0
  73. package/tests/components/user-permissions.test.tsx +142 -0
  74. package/tests/fixtures/theme-tokens.tsx +9 -1
  75. package/tests/logic/asset-url-build.test.ts +0 -1
  76. package/tests/logic/client-routes.test.ts +40 -0
  77. package/tests/logic/client-shell.test.tsx +10 -2
  78. package/tests/logic/config-example.test.ts +30 -0
  79. package/tests/logic/dev-client-watch.test.ts +8 -1
  80. package/tests/logic/route-overlay.test.tsx +42 -0
  81. package/tests/logic/scrollbar.test.ts +88 -0
  82. package/tests/logic/session-preparation.test.tsx +223 -0
  83. package/tests/logic/settings-permissions.test.ts +168 -0
  84. package/tests/logic/theme-tokens.test.ts +1 -0
  85. package/vite.config.ts +0 -22
  86. package/vitest.config.ts +1 -0
  87. package/client/agent-annotations-host.ts +0 -25
package/client/styles.css CHANGED
@@ -50,6 +50,7 @@
50
50
  --color-muted-foreground: var(--muted-foreground);
51
51
  --color-accent: var(--accent);
52
52
  --color-accent-foreground: var(--accent-foreground);
53
+ --color-hover: var(--hover);
53
54
  --color-destructive: var(--destructive);
54
55
  --color-border: var(--border);
55
56
  --color-input: var(--input);
@@ -138,16 +139,28 @@
138
139
 
139
140
  /* Application-wide interaction and badge treatment, outside generated primitives. */
140
141
  @layer base {
142
+ /* Whatever may be clicked shows the pointer, the plugins' pages included.
143
+ Tailwind 4 leaves buttons with the arrow; a control disabled (natively,
144
+ or as Base UI and ARIA mark it) keeps it, and a utility on an element
145
+ (`cursor-default`, `cursor-not-allowed`) still wins over this layer. */
141
146
  :is(
142
147
  button,
143
148
  [role='button'],
144
149
  [role='menuitem'],
150
+ [role='menuitemcheckbox'],
151
+ [role='menuitemradio'],
145
152
  [role='option'],
146
153
  [role='tab'],
147
154
  [role='checkbox'],
148
155
  [role='radio'],
149
156
  [role='switch'],
150
157
  [role='combobox'],
158
+ summary,
159
+ select,
160
+ label[for],
161
+ input[type='checkbox'],
162
+ input[type='radio'],
163
+ input[type='file'],
151
164
  a[href]
152
165
  ):not(:disabled):not([aria-disabled='true']):not([data-disabled]) {
153
166
  cursor: pointer;
@@ -161,14 +174,64 @@
161
174
  padding-inline: calc(var(--spacing) * 2.5);
162
175
  line-height: 1.4;
163
176
  }
177
+ /* The small badge, `data-size='sm'`: beside a title or a name, on a card or
178
+ a board's card, wherever several sit in a dense row. The default one is a
179
+ status standing on its own, as in a table's column or a record's
180
+ particulars. */
181
+ [data-slot='badge'][data-size='sm'] {
182
+ min-height: calc(var(--spacing) * 5.5);
183
+ padding-block: 0;
184
+ padding-inline: calc(var(--spacing) * 1.5);
185
+ font-size: var(--text-xs);
186
+ line-height: 1.3;
187
+ }
188
+
189
+ /* One focus for every control, across the application and the plugins'
190
+ pages: a half-strength border and a 2px halo at a fifth of the ring colour,
191
+ in place of the 3px ring at half of it that the primitives and pages draw,
192
+ which reads heavy. Unlayered, so it wins over their utilities without
193
+ editing them. A label wrapping a visually hidden control, such as an
194
+ option card, and a rich text editor's frame show their control's focus.
195
+ An invalid field keeps its red, and a destructive button its own colour. */
196
+ :is(
197
+ [data-slot='input'],
198
+ [data-slot='textarea'],
199
+ [data-slot='select-trigger'],
200
+ button,
201
+ [role='button'],
202
+ [role='tab'],
203
+ [role='menuitem'],
204
+ [role='checkbox'],
205
+ [role='switch'],
206
+ [role='radio'],
207
+ input[type='checkbox'],
208
+ input[type='radio'],
209
+ a[href]
210
+ ):focus-visible:not([aria-invalid='true']),
211
+ label:has(> .sr-only:focus-visible),
212
+ [data-slot='rich-text-editor']:focus-within,
213
+ [data-slot='input-group']:has(
214
+ [data-slot='input-group-control']:focus-visible
215
+ ):not(:has([aria-invalid='true'])) {
216
+ border-color: color-mix(in oklch, var(--ring) 50%, transparent);
217
+ box-shadow: 0 0 0 2px color-mix(in oklch, var(--ring) 20%, transparent);
218
+ }
219
+ [data-slot='button'].text-destructive:focus-visible:not([aria-invalid='true']) {
220
+ border-color: color-mix(in oklch, var(--destructive) 40%, transparent);
221
+ box-shadow: 0 0 0 2px color-mix(in oklch, var(--destructive) 20%, transparent);
222
+ }
164
223
 
165
- /* Shared overlay treatment lives outside generated shadcn primitives. */
224
+ /* Overlays in a light frame: a faint, blurred backdrop, and the content
225
+ framed by the general border alone, as a card is, without the primitive's
226
+ ring or a heavy shadow. The plugins' copies of these primitives take it
227
+ too; unlayered, so it wins over their utilities. */
166
228
  :is(
167
229
  [data-slot='dialog-overlay'],
168
230
  [data-slot='alert-dialog-overlay'],
169
231
  [data-slot='sheet-overlay']
170
232
  ) {
171
- background: var(--overlay-backdrop);
233
+ background: rgb(0 0 0 / 0.1);
234
+ backdrop-filter: blur(4px);
172
235
  }
173
236
  :is(
174
237
  [data-slot='dialog-content'],
@@ -176,7 +239,7 @@
176
239
  [data-slot='route-overlay-content']
177
240
  ) {
178
241
  border: 1px solid var(--border);
179
- box-shadow: var(--overlay-shadow);
242
+ box-shadow: none;
180
243
  }
181
244
  [data-slot='route-overlay-content'][data-presentation='dialog'] {
182
245
  border-radius: var(--radius-xl);
@@ -189,16 +252,13 @@
189
252
  :is([data-slot='dialog-description'], [data-slot='alert-dialog-description']) {
190
253
  line-height: 1.65;
191
254
  }
192
- :is(
193
- [data-slot='dialog-footer'],
194
- [data-slot='alert-dialog-footer'],
195
- [data-slot='route-overlay-footer']
196
- )
197
- > button {
255
+ :is([data-slot='dialog-footer'], [data-slot='alert-dialog-footer']) > button {
198
256
  min-height: calc(var(--spacing) * 10);
199
257
  padding-inline: calc(var(--spacing) * 4);
200
258
  }
201
- [data-slot='dialog-content'] > [data-slot='dialog-close'] {
259
+ /* Route overlays compose the dialog primitives, so their close button sits where a dialog's does. */
260
+ :is([data-slot='dialog-content'], [data-slot='route-overlay-content'])
261
+ > [data-slot='dialog-close'] {
202
262
  top: calc(var(--spacing) * 4);
203
263
  right: calc(var(--spacing) * 4);
204
264
  }
@@ -308,14 +368,65 @@
308
368
  padding-right: calc(var(--spacing) * 24);
309
369
  }
310
370
 
311
- /* Compact content can stay dense while the primary navigation remains comfortable.
312
- The primitive's important icon-mode sizes still control the collapsed rail. */
313
- :root[data-theme='compact'] .app-navigation [data-slot='sidebar-menu-button'] {
314
- height: calc(var(--spacing) * 12);
315
- padding-inline: calc(var(--spacing) * 4);
371
+ /* The navigation's rows are roomier than the sidebar primitive's: 10 units
372
+ high with half a unit between them, nested rows 8.5 high, in a sidebar 64
373
+ units wide (layouts/components/app-sidebar.tsx). The primitive's important
374
+ icon-mode sizes still control the collapsed rail. */
375
+ .app-navigation [data-slot='sidebar-menu'] {
376
+ gap: calc(var(--spacing) * 0.5);
377
+ }
378
+ /* Top-level groups only: a section nested in a menu sits flush. */
379
+ .app-navigation > nav > [data-slot='sidebar-group'] {
380
+ padding: calc(var(--spacing) * 2);
381
+ }
382
+ /* A section's heading reads as a heading over its entries, not as one of
383
+ them: half the menu's foreground, mixed toward its ground, so both colour
384
+ schemes follow. */
385
+ .app-navigation [data-sidebar='group-label'] {
386
+ height: calc(var(--spacing) * 8);
387
+ padding-inline: calc(var(--spacing) * 3.5);
388
+ color: color-mix(in oklch, var(--sidebar-foreground) 50%, var(--sidebar));
389
+ }
390
+ .app-navigation [data-slot='sidebar-menu-button'] {
391
+ height: calc(var(--spacing) * 10);
392
+ gap: calc(var(--spacing) * 2);
393
+ padding: calc(var(--spacing) * 2) calc(var(--spacing) * 3.5);
394
+ }
395
+ /* The primitive places a row's count and its expand button for 8-unit rows;
396
+ both are 5 units high, so this centres them on the taller rows. */
397
+ .app-navigation
398
+ [data-slot='sidebar-menu-item']
399
+ > :is([data-slot='sidebar-menu-badge'], [data-slot='sidebar-menu-action']) {
400
+ top: calc(var(--spacing) * 2.5);
401
+ }
402
+ .app-navigation
403
+ [data-slot='sidebar-menu-sub-item']
404
+ > [data-slot='sidebar-menu-action'] {
405
+ top: calc(var(--spacing) * 1.75);
406
+ }
407
+ .app-navigation [data-slot='sidebar-menu-button'] > svg {
408
+ width: calc(var(--spacing) * 4);
409
+ height: calc(var(--spacing) * 4);
410
+ }
411
+ /* An entry with a count (navigation-menu.tsx) leaves room for it at the
412
+ row's end, so a long label truncates before reaching it. */
413
+ .app-navigation
414
+ [data-slot='sidebar-menu-item']:has(> [data-slot='sidebar-menu-badge'])
415
+ > [data-slot='sidebar-menu-button'] {
416
+ padding-right: calc(var(--spacing) * 10);
316
417
  }
317
- :root[data-theme='compact'] .app-navigation [data-slot='sidebar-menu'] {
318
- gap: calc(var(--spacing) * 1.5);
418
+ .app-navigation [data-slot='sidebar-menu-sub'] {
419
+ gap: calc(var(--spacing) * 0.5);
420
+ padding-block: calc(var(--spacing) * 1);
421
+ }
422
+ .app-navigation [data-slot='sidebar-menu-sub-button'] {
423
+ height: calc(var(--spacing) * 8.5);
424
+ gap: calc(var(--spacing) * 2);
425
+ padding-inline: calc(var(--spacing) * 2.5);
426
+ }
427
+ .app-navigation [data-slot='sidebar-menu-sub-button'] > svg {
428
+ width: calc(var(--spacing) * 4);
429
+ height: calc(var(--spacing) * 4);
319
430
  }
320
431
 
321
432
  /* Brand tint belongs to the application shell, independent of each page's content. */
@@ -331,11 +442,20 @@
331
442
 
332
443
  /* Shared table hierarchy stays neutral so semantic status colors remain distinct. */
333
444
  [data-slot='table-header'] {
334
- background: color-mix(in srgb, var(--muted) 70%, var(--card));
445
+ background: var(--hover);
335
446
  }
336
447
  [data-slot='table-header'] [data-slot='table-row']:hover {
337
448
  background: transparent;
338
449
  }
450
+ /* A row under the pointer takes the --hover grey, as every hovered row and
451
+ list item does, rather than the primitive's half of --muted. Matched by
452
+ that class, so a row given another hover keeps it, and a selected row
453
+ keeps its own ground. */
454
+ [data-slot='table-row'][class~='hover:bg-muted/50']:hover:not(
455
+ [data-state='selected']
456
+ ) {
457
+ background: var(--hover);
458
+ }
339
459
  [data-slot='table-head'] {
340
460
  height: calc(var(--spacing) * 12);
341
461
  padding-inline: calc(var(--spacing) * 3);
@@ -355,12 +475,125 @@
355
475
  color: var(--primary);
356
476
  }
357
477
 
358
- /* Nested items keep a comfortable rhythm independently of compact content. */
359
- .app-navigation [data-slot='sidebar-menu-sub-button'] {
478
+ /* A card's frame in the general border, as every other line: the card
479
+ primitive and the plugins' copies draw their ring at a tenth of the
480
+ foreground. Matched by that class, so a card given another ring colour
481
+ keeps it; unlayered, so it wins over the utility. */
482
+ [data-slot='card'][class~='ring-foreground/10'] {
483
+ --tw-ring-color: var(--border);
484
+ }
485
+
486
+ /* Line tabs as a record page's tabs: from the left, the open one in the
487
+ foreground over a 2px line that sits on a thin rule under the row, the
488
+ others muted until hovered. Unlayered, so it wins over the primitive's
489
+ utilities. A row whose tabs keep off its edges sets --tabs-inset; its rule
490
+ still runs edge to edge. */
491
+ [data-slot='tabs-list'][data-variant='line'][data-orientation='horizontal'] {
492
+ width: 100%;
493
+ height: auto;
494
+ justify-content: flex-start;
495
+ gap: calc(var(--spacing) * 5);
496
+ padding: 0 var(--tabs-inset, 0px);
497
+ border-bottom: 1px solid var(--border);
498
+ }
499
+ [data-slot='tabs-list'][data-variant='line'][data-orientation='horizontal']
500
+ > [data-slot='tabs-trigger'] {
501
+ flex: none;
360
502
  height: calc(var(--spacing) * 10);
361
- padding-inline: calc(var(--spacing) * 3);
503
+ padding-inline: 0;
504
+ border-inline-width: 0;
505
+ color: var(--muted-foreground);
362
506
  }
363
- .app-navigation [data-slot='sidebar-menu-sub'] {
364
- gap: calc(var(--spacing) * 1.5);
365
- padding-block: calc(var(--spacing) * 1.5);
507
+ [data-slot='tabs-list'][data-variant='line'][data-orientation='horizontal']
508
+ > [data-slot='tabs-trigger']:is(:hover, [data-active]) {
509
+ color: var(--foreground);
510
+ }
511
+ [data-slot='tabs-list'][data-variant='line'][data-orientation='horizontal']
512
+ > [data-slot='tabs-trigger']::after {
513
+ bottom: -1px;
514
+ height: 2px;
515
+ }
516
+
517
+ /* Every scrollbar, across and down: a thin rounded bar in a faint grey on no
518
+ track, drawn only while its element scrolls or the mouse is near the edge
519
+ it runs along (revealScrollbarsWhileScrolling
520
+ in client/lib/scrollbar.ts marks it `data-scrolling` until it has been
521
+ still for a moment) and transparent otherwise, so showing it moves nothing;
522
+ a little firmer under the pointer. Chromium and Safari draw it through the
523
+ -webkit- pseudo-elements, which allow a thinner bar than
524
+ `scrollbar-width: thin`; they ignore those wherever the standard properties
525
+ are set (scrollbar-color is inherited), so the standard ones are given only
526
+ to a browser without them (Firefox). */
527
+ ::-webkit-scrollbar {
528
+ width: 6px;
529
+ height: 6px;
530
+ }
531
+ ::-webkit-scrollbar-track,
532
+ ::-webkit-scrollbar-corner {
533
+ background: transparent;
534
+ }
535
+ ::-webkit-scrollbar-thumb {
536
+ border-radius: 9999px;
537
+ background-color: transparent;
538
+ }
539
+ [data-scrolling]::-webkit-scrollbar-thumb {
540
+ background-color: color-mix(
541
+ in oklab,
542
+ var(--muted-foreground) 20%,
543
+ transparent
544
+ );
545
+ }
546
+ [data-scrolling]::-webkit-scrollbar-thumb:hover {
547
+ background-color: color-mix(
548
+ in oklab,
549
+ var(--muted-foreground) 30%,
550
+ transparent
551
+ );
552
+ }
553
+ @supports not selector(::-webkit-scrollbar) {
554
+ * {
555
+ scrollbar-width: thin;
556
+ scrollbar-color: transparent transparent;
557
+ }
558
+ [data-scrolling] {
559
+ scrollbar-color: color-mix(
560
+ in oklab,
561
+ var(--muted-foreground) 20%,
562
+ transparent
563
+ )
564
+ transparent;
565
+ }
566
+ }
567
+
568
+ /* A scroller whose bar always shows, thicker, to be dragged: one wider than
569
+ the page, such as a board, which a mouse without a sideways wheel moves
570
+ only by its bar. Plain rules beside the ones above rather than a utility,
571
+ which their unlayered rules would outrank. */
572
+ .scroll-shown::-webkit-scrollbar {
573
+ width: 8px;
574
+ height: 8px;
575
+ }
576
+ .scroll-shown::-webkit-scrollbar-thumb {
577
+ background-color: color-mix(
578
+ in oklab,
579
+ var(--muted-foreground) 25%,
580
+ transparent
581
+ );
582
+ }
583
+ .scroll-shown::-webkit-scrollbar-thumb:hover {
584
+ background-color: color-mix(
585
+ in oklab,
586
+ var(--muted-foreground) 40%,
587
+ transparent
588
+ );
589
+ }
590
+ @supports not selector(::-webkit-scrollbar) {
591
+ .scroll-shown {
592
+ scrollbar-color: color-mix(
593
+ in oklab,
594
+ var(--muted-foreground) 25%,
595
+ transparent
596
+ )
597
+ transparent;
598
+ }
366
599
  }
@@ -36,7 +36,7 @@ export function ThemeSettings(): ReactElement {
36
36
  aria-label={title}
37
37
  size='icon'
38
38
  variant='outline'
39
- className='size-10 rounded-xl border-border/70 bg-background/60 hover:bg-accent/50'
39
+ className='size-10 rounded-xl border-border bg-background/60 hover:bg-accent/50'
40
40
  />
41
41
  }
42
42
  >
@@ -70,13 +70,15 @@ export function ThemeSettings(): ReactElement {
70
70
  <label
71
71
  key={mode}
72
72
  className={cn(
73
- 'relative flex min-w-0 cursor-pointer items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-medium transition-colors select-none has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-ring',
73
+ 'relative flex min-w-0 cursor-pointer items-center justify-center gap-1.5 rounded-md px-2 py-1.5 text-xs font-medium transition-colors select-none',
74
74
  isSelected
75
75
  ? 'bg-background text-foreground shadow-xs'
76
76
  : 'text-muted-foreground hover:bg-background/40 hover:text-foreground',
77
77
  )}
78
78
  >
79
- <RadioGroupItem value={mode} className='sr-only' />
79
+ {/* `absolute` over the primitive's `relative`, which would keep
80
+ the hidden radio in the row and push the words aside. */}
81
+ <RadioGroupItem value={mode} className='absolute sr-only' />
80
82
  <ModeIcon aria-hidden='true' className='size-3.5 shrink-0' />
81
83
  <span className='whitespace-nowrap'>
82
84
  {t('appearance.' + mode, {
@@ -109,18 +111,21 @@ export function ThemeSettings(): ReactElement {
109
111
  <label
110
112
  key={item.id}
111
113
  className={cn(
112
- 'group relative flex cursor-pointer flex-col rounded-xl border p-2 text-left transition-all select-none has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-ring',
114
+ 'group relative flex cursor-pointer flex-col rounded-xl border p-2 text-left transition-all select-none',
113
115
  isSelected
114
116
  ? 'border-primary ring-2 ring-primary/20 bg-primary/5 shadow-xs'
115
- : 'border-border/70 hover:border-border hover:bg-muted/30',
117
+ : 'border-border hover:bg-muted/30',
116
118
  )}
117
119
  >
118
- <RadioGroupItem value={item.id} className='sr-only' />
120
+ <RadioGroupItem
121
+ value={item.id}
122
+ className='absolute sr-only'
123
+ />
119
124
  <div className='mb-2 h-16 w-full'>
120
125
  <div
121
126
  aria-hidden='true'
122
127
  data-theme={item.id}
123
- className='theme-preview relative flex h-full overflow-hidden rounded-lg border border-border/80 bg-background shadow-2xs'
128
+ className='theme-preview relative flex h-full overflow-hidden rounded-lg border border-border bg-background shadow-2xs'
124
129
  >
125
130
  {/* Mini Sidebar */}
126
131
  <div className='flex w-6 flex-col gap-1 border-r border-sidebar-border/50 bg-sidebar p-1'>
@@ -9,7 +9,7 @@
9
9
  ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
10
10
  'Courier New', monospace;
11
11
  --font-heading: var(--font-sans);
12
- --spacing: 0.2rem;
12
+ --spacing: 0.225rem;
13
13
  --text-xs: 0.75rem;
14
14
  --text-xs--line-height: calc(1 / 0.75);
15
15
  --text-sm: 0.875rem;
@@ -45,15 +45,11 @@
45
45
  --shadow-xl:
46
46
  0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
47
47
  --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
48
- --radius: 0.375rem;
48
+ --radius: 0.5rem;
49
49
  --background: oklch(0.985 0 0);
50
50
  --foreground: oklch(0.16 0 0);
51
51
  --card: oklch(1 0 0);
52
52
  --card-foreground: oklch(0.16 0 0);
53
- --overlay-backdrop: rgb(15 23 42 / 0.28);
54
- --overlay-shadow:
55
- 0 24px 64px -16px rgb(15 23 42 / 0.22),
56
- 0 8px 24px -12px rgb(15 23 42 / 0.16);
57
53
  --popover: oklch(1 0 0);
58
54
  --popover-foreground: oklch(0.16 0 0);
59
55
  --primary: #002ad1;
@@ -71,8 +67,9 @@
71
67
  --muted-foreground: oklch(0.52 0 0);
72
68
  --accent: rgb(237 242 255);
73
69
  --accent-foreground: rgb(0 42 209);
70
+ --hover: color-mix(in srgb, var(--muted) 70%, var(--card));
74
71
  --destructive: oklch(0.577 0.245 27.325);
75
- --border: oklch(0.9 0 0);
72
+ --border: oklch(0.9 0 0 / 60%);
76
73
  --input: oklch(0.9 0 0);
77
74
  --ring: var(--primary);
78
75
  --chart-1: oklch(0.646 0.222 41.116);
@@ -84,8 +81,8 @@
84
81
  --sidebar-foreground: var(--card-foreground);
85
82
  --sidebar-primary: color-mix(in srgb, var(--primary) 8%, var(--sidebar));
86
83
  --sidebar-primary-foreground: var(--primary);
87
- --sidebar-accent: var(--accent);
88
- --sidebar-accent-foreground: var(--accent-foreground);
84
+ --sidebar-accent: var(--hover);
85
+ --sidebar-accent-foreground: var(--sidebar-foreground);
89
86
  --sidebar-border: var(--border);
90
87
  --sidebar-ring: var(--ring);
91
88
  }
@@ -96,9 +93,6 @@
96
93
  --foreground: oklch(0.96 0 0);
97
94
  --card: oklch(0.18 0 0);
98
95
  --card-foreground: oklch(0.985 0 0);
99
- --overlay-backdrop: rgb(0 0 0 / 0.58);
100
- --overlay-shadow:
101
- 0 24px 64px -16px rgb(0 0 0 / 0.6), 0 8px 24px -12px rgb(0 0 0 / 0.4);
102
96
  --popover: oklch(0.18 0 0);
103
97
  --popover-foreground: oklch(0.985 0 0);
104
98
  --primary: #8ca9ff;
@@ -113,6 +107,7 @@
113
107
  --muted-foreground: oklch(0.7 0 0);
114
108
  --accent: rgb(30 42 72);
115
109
  --accent-foreground: rgb(174 194 255);
110
+ --hover: color-mix(in srgb, var(--muted) 70%, var(--card));
116
111
  --destructive: oklch(0.704 0.191 22.216);
117
112
  --border: oklch(1 0 0 / 10%);
118
113
  --input: oklch(1 0 0 / 15%);
@@ -126,8 +121,8 @@
126
121
  --sidebar-foreground: var(--card-foreground);
127
122
  --sidebar-primary: color-mix(in srgb, var(--primary) 8%, var(--sidebar));
128
123
  --sidebar-primary-foreground: var(--primary);
129
- --sidebar-accent: var(--accent);
130
- --sidebar-accent-foreground: var(--accent-foreground);
124
+ --sidebar-accent: var(--hover);
125
+ --sidebar-accent-foreground: var(--sidebar-foreground);
131
126
  --sidebar-border: var(--border);
132
127
  --sidebar-ring: var(--ring);
133
128
  }
@@ -51,10 +51,6 @@
51
51
  --foreground: oklch(0.16 0 0);
52
52
  --card: oklch(1 0 0);
53
53
  --card-foreground: oklch(0.16 0 0);
54
- --overlay-backdrop: rgb(15 23 42 / 0.28);
55
- --overlay-shadow:
56
- 0 24px 64px -16px rgb(15 23 42 / 0.22),
57
- 0 8px 24px -12px rgb(15 23 42 / 0.16);
58
54
  --popover: oklch(1 0 0);
59
55
  --popover-foreground: oklch(0.16 0 0);
60
56
  --primary: #002ad1;
@@ -72,8 +68,9 @@
72
68
  --muted-foreground: oklch(0.52 0 0);
73
69
  --accent: rgb(237 242 255);
74
70
  --accent-foreground: rgb(0 42 209);
71
+ --hover: color-mix(in srgb, var(--muted) 70%, var(--card));
75
72
  --destructive: oklch(0.577 0.245 27.325);
76
- --border: oklch(0.9 0 0);
73
+ --border: oklch(0.9 0 0 / 60%);
77
74
  --input: oklch(0.9 0 0);
78
75
  --ring: var(--primary);
79
76
  --chart-1: oklch(0.646 0.222 41.116);
@@ -85,8 +82,8 @@
85
82
  --sidebar-foreground: var(--card-foreground);
86
83
  --sidebar-primary: color-mix(in srgb, var(--primary) 8%, var(--sidebar));
87
84
  --sidebar-primary-foreground: var(--primary);
88
- --sidebar-accent: var(--accent);
89
- --sidebar-accent-foreground: var(--accent-foreground);
85
+ --sidebar-accent: var(--hover);
86
+ --sidebar-accent-foreground: var(--sidebar-foreground);
90
87
  --sidebar-border: var(--border);
91
88
  --sidebar-ring: var(--ring);
92
89
  }
@@ -98,9 +95,6 @@
98
95
  --foreground: oklch(0.96 0 0);
99
96
  --card: oklch(0.18 0 0);
100
97
  --card-foreground: oklch(0.985 0 0);
101
- --overlay-backdrop: rgb(0 0 0 / 0.58);
102
- --overlay-shadow:
103
- 0 24px 64px -16px rgb(0 0 0 / 0.6), 0 8px 24px -12px rgb(0 0 0 / 0.4);
104
98
  --popover: oklch(0.18 0 0);
105
99
  --popover-foreground: oklch(0.985 0 0);
106
100
  --primary: #8ca9ff;
@@ -115,6 +109,7 @@
115
109
  --muted-foreground: oklch(0.7 0 0);
116
110
  --accent: rgb(30 42 72);
117
111
  --accent-foreground: rgb(174 194 255);
112
+ --hover: color-mix(in srgb, var(--muted) 70%, var(--card));
118
113
  --destructive: oklch(0.704 0.191 22.216);
119
114
  --border: oklch(1 0 0 / 10%);
120
115
  --input: oklch(1 0 0 / 15%);
@@ -128,8 +123,8 @@
128
123
  --sidebar-foreground: var(--card-foreground);
129
124
  --sidebar-primary: color-mix(in srgb, var(--primary) 8%, var(--sidebar));
130
125
  --sidebar-primary-foreground: var(--primary);
131
- --sidebar-accent: var(--accent);
132
- --sidebar-accent-foreground: var(--accent-foreground);
126
+ --sidebar-accent: var(--hover);
127
+ --sidebar-accent-foreground: var(--sidebar-foreground);
133
128
  --sidebar-border: var(--border);
134
129
  --sidebar-ring: var(--ring);
135
130
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nocobase/app-template-default",
3
3
  "displayName": "Default Template",
4
- "version": "1.0.0-beta.67",
4
+ "version": "1.0.0-beta.69",
5
5
  "type": "module",
6
6
  "prettier": "@nocobase/dev-config/prettier",
7
7
  "engines": {
@@ -9,13 +9,12 @@
9
9
  },
10
10
  "nocobase": {
11
11
  "templateKind": "app",
12
- "defaultTemplateVersion": "1.0.0-beta.67"
12
+ "defaultTemplateVersion": "1.0.0-beta.69"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@ai-sdk/react": "2.0.0",
16
16
  "@base-ui/react": "^1.6.0",
17
17
  "@dicebear/core": "^9.4.3",
18
- "@gchust/agent-annotations": "0.1.9",
19
18
  "@hookform/resolvers": "^5.0.1",
20
19
  "@playwright/test": "^1.62.1",
21
20
  "@refinedev/cli": "^2.16.48",
@@ -27,6 +26,7 @@
27
26
  "@tailwindcss/postcss": "^4.1.4",
28
27
  "@tailwindcss/vite": "^4.1.8",
29
28
  "@tanstack/react-query": "^5.81.5",
29
+ "@tanstack/react-table": "^8.2.6",
30
30
  "@testing-library/jest-dom": "^7.0.0",
31
31
  "@testing-library/react": "^16.3.2",
32
32
  "@testing-library/user-event": "^14.6.3",
@@ -75,13 +75,13 @@
75
75
  "vite": "^6.3.5",
76
76
  "vitest": "^4.1.10",
77
77
  "tar": "^7.5.22",
78
- "@nocobase/app-client": "^3.0.0-beta.1",
78
+ "@nocobase/app-client": "^3.0.0-beta.2",
79
79
  "@nocobase/app-plugin-template-print": "0.0.2-beta.2",
80
- "@nocobase/app-skills": "0.1.0-beta.34",
81
- "@nocobase/app-testing": "0.1.0-beta.3",
80
+ "@nocobase/app-skills": "0.1.0-beta.37",
81
+ "@nocobase/app-testing": "0.1.0-beta.4",
82
82
  "@nocobase/app-websocket": "^0.0.2-beta.0",
83
- "@nocobase/dev-config": "0.1.0-beta.22",
84
- "@nocobase/drive": "^0.1.0-beta.5",
83
+ "@nocobase/drive": "^0.1.0-beta.6",
84
+ "@nocobase/dev-config": "0.1.0-beta.23",
85
85
  "@nocobase/logging": "^0.1.0-beta.7",
86
86
  "@nocobase/session": "^0.1.0-beta.3"
87
87
  },
@@ -142,35 +142,35 @@
142
142
  "@oclif/core": "^4.14.0",
143
143
  "better-auth": "1.7.5",
144
144
  "hono": "^4.13.2",
145
- "@nocobase/app-plugin-api-keys": "^1.0.0-beta.16",
146
- "@nocobase/app-plugin-authentication": "^2.0.0-beta.7",
147
- "@nocobase/app-plugin-authorization": "^1.0.0-beta.27",
148
- "@nocobase/app-plugin-database-explorer": "^1.0.0-beta.14",
149
- "@nocobase/app-plugin-file": "^1.0.0-beta.23",
145
+ "@nocobase/app-plugin-api-keys": "^1.0.0-beta.17",
146
+ "@nocobase/app-plugin-authentication": "^2.0.0-beta.10",
147
+ "@nocobase/app-plugin-authorization": "^1.0.0-beta.28",
148
+ "@nocobase/app-plugin-database-explorer": "^1.0.0-beta.15",
150
149
  "@nocobase/app-plugin-i18n": "^1.0.0-beta.16",
151
- "@nocobase/app-plugin-mail": "^1.0.0-beta.13",
150
+ "@nocobase/app-plugin-mail": "^1.0.0-beta.14",
151
+ "@nocobase/app-plugin-file": "^1.0.0-beta.24",
152
152
  "@nocobase/app-plugin-notification": "^1.0.0-beta.27",
153
- "@nocobase/app-plugin-notification-in-app": "^1.0.0-beta.24",
154
- "@nocobase/app-plugin-notification-providers": "^0.2.0-beta.11",
153
+ "@nocobase/app-plugin-notification-in-app": "^1.0.0-beta.25",
154
+ "@nocobase/app-plugin-notification-providers": "^0.2.0-beta.12",
155
155
  "@nocobase/app-plugin-scheduler": "^1.0.0-beta.17",
156
- "@nocobase/app-plugin-users": "^2.0.0-beta.4",
157
- "@nocobase/app-server": "^2.0.0-beta.5",
156
+ "@nocobase/app-plugin-users": "^2.0.0-beta.5",
158
157
  "@nocobase/config": "^0.1.0-beta.3",
159
- "@nocobase/db": "^1.0.0-beta.19",
160
- "@nocobase/db-sqlite": "^0.1.0-beta.5",
158
+ "@nocobase/app-server": "^2.0.0-beta.8",
159
+ "@nocobase/db": "^1.0.0-beta.21",
161
160
  "@nocobase/service-provider": "^0.0.2-beta.2",
161
+ "@nocobase/db-sqlite": "^0.1.0-beta.6",
162
162
  "@nocobase/snowflake": "^1.0.0-beta.4",
163
163
  "@nocobase/app-plugin-authz-default-access": "^1.0.0-beta.13",
164
164
  "@nocobase/app-plugin-authz-sharing-rules": "^1.0.0-beta.13",
165
165
  "@nocobase/app-plugin-authz-restriction-rules": "^1.0.0-beta.12",
166
- "@nocobase/authorization": "^1.0.0-beta.13",
166
+ "@nocobase/authorization": "^1.0.0-beta.14",
167
167
  "@nocobase/queue": "^0.1.0-beta.9",
168
168
  "@nocobase/jobs": "^0.1.0-beta.3",
169
- "@nocobase/lifecycle": "^0.1.0-beta.1",
169
+ "@nocobase/lifecycle": "^0.1.0-beta.2",
170
170
  "@nocobase/i18n": "^1.0.0-beta.7",
171
- "@nocobase/caching": "^0.1.0-beta.3",
172
171
  "@nocobase/repository-input": "^0.1.0-beta.3",
173
- "@nocobase/app-cli": "^1.0.0-beta.21"
172
+ "@nocobase/caching": "^0.1.0-beta.3",
173
+ "@nocobase/app-cli": "^1.0.0-beta.23"
174
174
  },
175
175
  "description": "NocoBase 3 application template with the standard product capabilities.",
176
176
  "imports": {
@@ -178,7 +178,8 @@
178
178
  "#hooks/*": "./client/hooks/*.js",
179
179
  "#lib/*": "./client/lib/*.js",
180
180
  "#extensions/*": "./client/extensions/*.js",
181
- "#pages/*": "./client/pages/*.js"
181
+ "#pages/*": "./client/pages/*.js",
182
+ "#components/data-table": "./client/components/data-table/index.js"
182
183
  },
183
184
  "scripts": {
184
185
  "postinstall": "nocobase skills sync",
package/server/app.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { MailerProvider } from '@nocobase/app-server/mailer';
1
2
  import type { Application } from '@nocobase/app-server/application';
2
3
  import { DatabaseProvider } from '@nocobase/app-server/database';
3
4
  import { I18nProvider, i18nHttpMiddleware } from '@nocobase/app-server/i18n';
@@ -33,6 +34,7 @@ export function createApp(runtime: AppRuntimeContext): Application {
33
34
  app.addServiceProvider(SecretsProvider);
34
35
  app.addServiceProvider(SessionProvider);
35
36
  app.addServiceProvider(DriveProvider);
37
+ app.addServiceProvider(MailerProvider);
36
38
  app.addServiceProvider(QueueServiceProvider, {
37
39
  nodeEnv: runtime.env.NODE_ENV,
38
40
  });