@nocobase/app-template-default 1.0.0-beta.88 → 1.0.0-beta.90
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/.agents/rules/components.md +3 -1
- package/.agents/rules/shell.md +12 -2
- package/AGENTS.md +1 -1
- package/LICENSE.txt +129 -0
- package/client/components/back-button.tsx +37 -12
- package/client/components/brand-loading-mark.tsx +5 -2
- package/client/components/brand-mark-geometry.ts +3 -3
- package/client/components/breadcrumbs.tsx +47 -3
- package/client/components/data-table/data-table.tsx +90 -19
- package/client/components/data-table/pagination.tsx +17 -2
- package/client/components/list-feedback.tsx +31 -22
- package/client/components/loading.tsx +127 -16
- package/client/components/pending-button.tsx +111 -0
- package/client/components/role-list.tsx +7 -0
- package/client/components/route-child-page.tsx +72 -1
- package/client/components/route-overlay.tsx +149 -15
- package/client/components/task-progress.tsx +155 -0
- package/client/components/use-route-overlay.ts +8 -11
- package/client/extensions/nocobase-file-component-ui/components/file-upload-field.tsx +62 -32
- package/client/extensions/nocobase-file-component-ui/locales/de-DE.ts +2 -0
- package/client/extensions/nocobase-file-component-ui/locales/en-US.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/es-ES.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/fr-FR.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/id-ID.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/ja-JP.ts +2 -0
- package/client/extensions/nocobase-file-component-ui/locales/pt-BR.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/vi-VN.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/zh-CN.ts +1 -0
- package/client/extensions/nocobase-file-component-ui/locales/zh-TW.ts +1 -0
- package/client/extensions/nocobase-workflow-management/README.md +5 -5
- package/client/extensions/nocobase-workflow-management/definition-list.tsx +2 -9
- package/client/extensions/nocobase-workflow-management/execution-list.tsx +2 -4
- package/client/layouts/app-layout.tsx +0 -4
- package/client/lib/navigation-transform.ts +29 -1
- package/client/locales/system/en-US.ts +23 -0
- package/client/locales/system/zh-CN.ts +23 -0
- package/client/pages/home/index.tsx +66 -51
- package/client/pages/settings/database-explorer/en-US.ts +33 -0
- package/client/pages/settings/database-explorer/index.tsx +358 -0
- package/client/pages/settings/database-explorer/routes.ts +14 -0
- package/client/pages/settings/database-explorer/zh-CN.ts +30 -0
- package/client/pages/settings/default-access.tsx +1 -1
- package/client/pages/settings/roles.tsx +27 -27
- package/client/pages/settings/routes.ts +15 -7
- package/client/pages/settings/user-detail.tsx +3 -17
- package/client/pages/settings/user-form.tsx +9 -15
- package/client/pages/settings/users.tsx +11 -27
- package/client/plugins.ts +8 -1
- package/client/routes.ts +2 -1
- package/client/routing/client-route.tsx +199 -37
- package/client/routing/navigation-destination.ts +42 -0
- package/client/routing/route-leave-boundary.tsx +13 -1
- package/client/styles.css +18 -1
- package/config.example.yml +17 -11
- package/index.html +8 -11
- package/package.json +27 -27
- package/server/config/auth.ts +1 -1
- package/server/config/index.ts +3 -0
- package/server/config/jobs.ts +16 -8
- package/server/config/queue.ts +14 -13
- package/server/config/realtime.ts +4 -0
- package/tests/client/auth-i18n.test.tsx +1 -1
- package/tests/client/authenticated-client-fixture.test.tsx +37 -0
- package/tests/client/back-button.test.tsx +34 -0
- package/tests/client/client-route-recovery.test.tsx +93 -1
- package/tests/client/client-routes.test.ts +5 -3
- package/tests/client/database-explorer-integration.test.tsx +30 -0
- package/tests/client/database-explorer.test.tsx +152 -0
- package/tests/client/foundation-integration.test.ts +42 -29
- package/tests/client/home.test.tsx +26 -85
- package/tests/client/layout-i18n.test.tsx +83 -2
- package/tests/client/list-feedback.test.tsx +5 -5
- package/tests/client/list-loading.test.tsx +397 -0
- package/tests/client/loading.test.tsx +20 -11
- package/tests/client/manual-list-retry.test.tsx +2 -2
- package/tests/client/navigation-parent.test.tsx +172 -0
- package/tests/client/pending-button.test.tsx +106 -0
- package/tests/client/permission-management-tabs.test.tsx +48 -12
- package/tests/client/primitive-labels.test.tsx +2 -2
- package/tests/client/role-return.test.tsx +21 -3
- package/tests/client/roles.test.tsx +84 -3
- package/tests/client/route-leave-recheck.test.tsx +74 -0
- package/tests/client/route-presentation.test.tsx +557 -0
- package/tests/client/session-preparation.test.tsx +20 -17
- package/tests/client/settings-continuity.test.tsx +35 -11
- package/tests/client/settings-home.test.tsx +4 -2
- package/tests/client/settings-idle-list.test.tsx +124 -0
- package/tests/client/settings-navigation.test.ts +20 -0
- package/tests/client/user-drawer.test.tsx +3 -4
- package/tests/fixtures/dialog-footer.html +12 -0
- package/tests/fixtures/dialog-footer.tsx +215 -0
- package/tests/helpers/authenticated-client.tsx +77 -22
- package/tests/playwright/dialog-footer.test.ts +227 -0
- package/tests/project/bootstrap-loading.test.ts +60 -0
- package/tests/server/app-server.test.ts +18 -12
- package/tests/server/auth-email-config.test.ts +21 -0
- package/tests/server/backends-config.test.ts +118 -0
- package/tests/server/concurrency.test.ts +1 -1
- package/tests/server/config.test.ts +22 -14
- package/tests/server/lifecycle.test.ts +2 -2
- package/tests/server/realtime-config.test.ts +114 -0
- package/client/pages/home/capabilities.ts +0 -62
- package/client/pages/home/prompt-dialog.tsx +0 -122
|
@@ -15,7 +15,7 @@ yes n | pnpm exec shadcn add card
|
|
|
15
15
|
pnpm exec prettier --write client/components/ui/card.tsx
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Build your own components by composing these primitives, and put them in `client/components/`. A
|
|
18
|
+
Build your own components by composing these primitives, and put them in `client/components/`. A covering child page, independent detail or edit declares its complete navigation with `breadcrumb` or `usePageBreadcrumb`; the shell renders desktop breadcrumbs and the mobile parent arrow. Declare dynamic/logic parents before loading succeeds and keep them on failure. Use `BackButton` only without that shell navigation, with route/access context for target validation; never show duplicate return entries.
|
|
19
19
|
|
|
20
20
|
**Look components up in the shadcn skill.** The development Skill carries the shadcn/ui skill, unchanged from shadcn 4.21.0, in `.agents/skills/nocobase-app-development/references/frontend/shadcn/`. Read [the shadcn guide](../skills/nocobase-app-development/references/frontend/references/shadcn.md) first: it routes to the skill's rules and states where this application departs from them, among others `pnpm exec shadcn` instead of `npx shadcn@latest`, no `shadcn apply` or `--preset`, and create, edit and detail as `RouteDialog` or `RouteDrawer` child routes rather than a `Sheet`. `pnpm exec shadcn docs <name>` gives one primitive's documentation and examples for the Base UI version. For a whole page, start from the worked example that [the example index](../skills/nocobase-app-development/references/frontend/references/example.md) indexes.
|
|
21
21
|
|
|
@@ -31,6 +31,8 @@ Style with the semantic Tailwind tokens — `bg-background`, `text-foreground`,
|
|
|
31
31
|
|
|
32
32
|
The default brand palette and density live in `client/theme/themes/*.css`; shared hover/cursor and Badge geometry live in `client/styles.css`, outside generated shadcn primitives. For semantic status labels, use `StatusBadge` from `client/components/status-badge.tsx` with `neutral`, `info`, `warning`, or `success`; choose the tone by meaning and keep the visible status text. A badge beside a title or on a dense card is the small one, `size='sm'` on `StatusBadge` or `data-size='sm'` on `Badge`. Reuse these shared components and tokens in new pages so template upgrades can carry the design consistently.
|
|
33
33
|
|
|
34
|
+
Dialog and AlertDialog footer actions use the shared semantic-slot sizing rule in `client/styles.css`, including linked Buttons and RouteOverlay's composed DialogFooter. Keep route links and pending guards; avoid page height/padding patches. The [footer contract](../skills/nocobase-app-development/references/frontend/references/overlay.md#footer-ownership) explains precedence, wrapping and the source upgrade path for existing applications.
|
|
35
|
+
|
|
34
36
|
**Follow the UI guidelines.** [The UI guidelines](../skills/nocobase-app-development/references/frontend/ui-guidelines.md) set the conventions every page follows, among them the page header and the single placement of the primary action, empty states, row actions behind one "…" menu, dates picked from a `Calendar` in a `Popover` rather than a native date input, and medium-width drawers; the frontend workflow says how much of it to read for a change.
|
|
35
37
|
|
|
36
38
|
## Standard process management
|
package/.agents/rules/shell.md
CHANGED
|
@@ -4,6 +4,8 @@ Read this before changing the layouts, header, sidebar, breadcrumbs, account men
|
|
|
4
4
|
|
|
5
5
|
## Breadcrumbs and layouts
|
|
6
6
|
|
|
7
|
+
Both automatic and explicit parents pass through `client/routing/navigation-destination.ts`, using the shell's existing route/access context. Keep permission loading fail-closed, check every target ancestor, and remove denied parent names. Desktop breadcrumbs and the mobile parent arrow use the same resolved levels; mobile keeps the current title and omits the duplicate brand. Follow the [detail/edit generation contract](../skills/nocobase-app-development/references/frontend/references/navigation.md), including stable parents during record failures, source-tab state and direct-entry defaults. Do not add a second page BackButton under the shell.
|
|
8
|
+
|
|
7
9
|
Layouts own breadcrumb route context and render the current page's trail in the header after the sidebar toggle (`client/components/breadcrumbs.tsx`): the route trail by default, or the trail a page declares with `usePageBreadcrumb` from `@nocobase/app-client` when it names records. Pages do not render breadcrumbs themselves. `AppRouter` selects routes and layouts. See [pages and routes](../skills/nocobase-app-development/references/frontend/references/page.md) for each layout's scope.
|
|
8
10
|
|
|
9
11
|
The route-tree provider remembers each visited ancestor's query while its descendants are open. Default breadcrumbs and back links restore that query, including an empty one, instead of applying detail-only parameters to the list. Keep this provider mounted above page navigation. Explicit breadcrumb/back destinations remain application-owned. See the linked pages-and-routes reference for deep-link and reload fallbacks.
|
|
@@ -24,13 +26,13 @@ Theme lives in the account menu as a submenu beside Language, rather than a sepa
|
|
|
24
26
|
|
|
25
27
|
The account menu checks Better Auth sign-out results before refreshing the session and shows a localized error toast for API or network failures. Preserve this behavior when upgrading the shell; navigation alone does not revoke a session.
|
|
26
28
|
|
|
27
|
-
The authorization provider clears the permission snapshot before rendering a new session. Route navigation and page guards subscribe to the authorization revision; preserve these checks when customizing the shell so account changes and permission updates take effect without a reload. Keep `RouteLeaveBoundary` in `AppLayout`, outside all page Activities including nested routes, so `useSettingsLeave` navigation guards, confirmation UI and `beforeunload` survive permission checks. Clear retained guards on denial and session changes. Restored Activity Effects re-run existing data requests. Pending checks hide protected content with React Activity while retaining drafts; failed checks unmount it and deny access. Body portals must detach in an Effect cleanup so they also disappear while the page is hidden; use `BodyPortal` from `#components/body-portal` for application-owned content mounted on `document.body`.
|
|
29
|
+
The authorization provider clears the permission snapshot before rendering a new session. Route navigation and page guards subscribe to the authorization revision; preserve these checks when customizing the shell so account changes and permission updates take effect without a reload. Keep `RouteLeaveBoundary` in `AppLayout`, outside all page Activities including nested routes, so `useSettingsLeave` navigation guards, confirmation UI and `beforeunload` survive permission checks. Clear retained guards on denial and session changes. Restored Activity Effects re-run existing data requests. Keep each resolved route module for the same loader and retry attempt when Activity reconnects; namespace-wrapping loaders may return a new component type on every invocation and discard form state if invoked again. Pending checks hide protected content with React Activity while retaining drafts; failed checks unmount it and deny access. Body portals must detach in an Effect cleanup so they also disappear while the page is hidden; use `BodyPortal` from `#components/body-portal` for application-owned content mounted on `document.body`.
|
|
28
30
|
|
|
29
31
|
The account menu language control in `client/layouts/components/language-switcher.tsx` uses a shadcn submenu with radio items. Render it inside `DropdownMenuContent` to preserve menu keyboard navigation and selection semantics.
|
|
30
32
|
|
|
31
33
|
## Branded loading
|
|
32
34
|
|
|
33
|
-
Use `Loading` for page or panel waits and `Spinner` (the shadcn-compatible adapter to `BrandSpinner`) for inline pending actions, uploads, and refresh indicators. Both share the stable NocoBase mark and orbit animation; do not introduce independent Lucide spinners for application loading. Keep skeletons for content placeholders and progress bars for determinate progress. `index.html` has the matching pre-JavaScript mark and must stay visually consistent. All animations respect reduced motion.
|
|
35
|
+
Follow [pending feedback](../skills/nocobase-app-development/references/pending-feedback.md) for shared 300ms display and 10s long-wait guidance. Use `PendingButton` with real `submitting` for native form actions; preserve the shared submission lock. Use `Loading` for page or panel waits and `Spinner` (the shadcn-compatible adapter to `BrandSpinner`) for inline pending actions, uploads, and refresh indicators. Both share the stable NocoBase mark and orbit animation; do not introduce independent Lucide spinners for application loading. Keep skeletons for content placeholders and progress bars for determinate progress. `index.html` has the matching pre-JavaScript mark and must stay visually consistent. All animations respect reduced motion.
|
|
34
36
|
|
|
35
37
|
The shared list of shell behaviors to keep when customizing is in [customizing the shell](../skills/nocobase-app-development/references/frontend/references/shell.md).
|
|
36
38
|
|
|
@@ -39,3 +41,11 @@ The signed-in region waits in `client/routing/session-preparation.tsx` before mo
|
|
|
39
41
|
## List context
|
|
40
42
|
|
|
41
43
|
Keep the identity-keyed `SessionPreparation` boundary: mounted private list conditions and ancestor query memory must be discarded when the signed-in user changes. Public list state uses `useTableUrlState`; tabbed datasets use `useListTabs`, with complete owned parameter keys and permission-filtered tabs. Follow the synchronized [list navigation rules](../skills/nocobase-app-development/references/frontend/references/child-routes.md#list-tab-breadcrumbs-and-private-conditions). Default back links use an empty ancestor query when no source visit exists; explicit return destinations remain application-owned.
|
|
44
|
+
|
|
45
|
+
## Page data freshness
|
|
46
|
+
|
|
47
|
+
Follow the synchronized [page data freshness rules](../skills/nocobase-app-development/references/frontend/references/page-freshness.md): ordinary pages have no persistent Refresh action. Preserve contextual read retries, write invalidation and live progress updates. Dependency upgrades do not rewrite application-owned pages or installed Registry source; reconcile them through the template upgrade workflow.
|
|
48
|
+
|
|
49
|
+
## Features and extensions
|
|
50
|
+
|
|
51
|
+
Features and extensions belongs in System settings under the System category, alongside Database Explorer. Use the permission-filtered settings catalog rather than a separate sidebar footer entry. Its detail breadcrumb includes System settings and returns to the filtered catalog. The homepage retains its discovery link.
|
package/AGENTS.md
CHANGED
|
@@ -63,7 +63,7 @@ A bare create-app shows Home to signed-in users and, only to unrestricted admini
|
|
|
63
63
|
|
|
64
64
|
### Pages and routes
|
|
65
65
|
|
|
66
|
-
Declare every page in `client/routes.ts` with `defineAppRoutes()` and keep its component behind a lazy `componentLoader` whose module default-exports it. Never write the deployment base path such as `/main` into a route — the runtime restores it. `auth` controls browser navigation only and is not server security. Declare `navigation` on the route to put it in a menu, and always declare `authz` on the first page of every path; the page id identifies stored page grants, so changing it requires migrating the grants that reference it. Read [pages and routes](.agents/skills/nocobase-app-development/references/frontend/references/page.md), and [overlays](.agents/skills/nocobase-app-development/references/frontend/references/overlay.md) before implementing dialogs, drawers or close guards.
|
|
66
|
+
Declare every page in `client/routes.ts` with `defineAppRoutes()` and keep its component behind a lazy `componentLoader` whose module default-exports it. Never write the deployment base path such as `/main` into a route — the runtime restores it. `auth` controls browser navigation only and is not server security. Declare `navigation` on the route to put it in a menu, and always declare `authz` on the first page of every path; the page id identifies stored page grants, so changing it requires migrating the grants that reference it. Every new independent detail/edit page follows the [navigation pattern](.agents/skills/nocobase-app-development/references/frontend/references/navigation.md), including a parent during loading/errors and mobile return; omit duplicate page back links under the shell. Read [pages and routes](.agents/skills/nocobase-app-development/references/frontend/references/page.md), and [overlays](.agents/skills/nocobase-app-development/references/frontend/references/overlay.md) before implementing dialogs, drawers or close guards.
|
|
67
67
|
|
|
68
68
|
Every route listed as a page permission needs a localized display name: use its existing navigation/breadcrumb title, or add its application route name to `client/pages/settings/page-permission-titles.ts` for an overlay or other page with no title metadata. Do not add breadcrumbs/menus just to label an overlay. Run `tests/client/page-permission-titles.test.ts`; it requires non-empty English/Chinese labels and rejects missing or stale mappings. Never rename route/permission ids or change `authz` to fix display text.
|
|
69
69
|
|
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Updated Date: November 7, 2026
|
|
2
|
+
|
|
3
|
+
NocoBase License Agreement
|
|
4
|
+
|
|
5
|
+
NOCOBASE PTE. LTD., a Singaporean Exempt Private Company Limited by Shares with its principal place of business located at 112 ROBINSON ROAD, #03-01, SINGAPORE ("The Company") https://www.nocobase.com/ issues this License Agreement ("Agreement") to you. You, as an individual or a company ("The User"), will be deemed to voluntarily accept all terms of this Agreement by using NocoBase (including but not limited to obtaining NocoBase source code, installation package, or image in any form, installing and using NocoBase, developing Applications with NocoBase, purchasing NocoBase commercial license and services). If the User does not agree to any term of this Agreement, or cannot accurately understand our interpretation of the relevant terms, please stop using it immediately.
|
|
6
|
+
|
|
7
|
+
This Agreement applies to any use, quotation, contract, invoice, and all software delivered by the Company. The User and the Company or NocoBase's agents need not sign a separate license agreement for the sale and delivery of the software.
|
|
8
|
+
|
|
9
|
+
This Agreement applies to NocoBase 3 and later versions. NocoBase 2 and earlier versions are independent products and remain subject to their original license agreement. Where a NocoBase 2 Commercial Edition user uses NocoBase 3 or later versions under a policy announced by the Company, that use is subject to this Agreement.
|
|
10
|
+
|
|
11
|
+
The Company reserves the right to formulate and modify this Agreement from time to time as needed. If there are changes, the Company will announce them in the form of website announcements, without further individual notification. The changed Agreement will automatically take effect once it is announced, becoming part of this Agreement.
|
|
12
|
+
|
|
13
|
+
==============
|
|
14
|
+
1. Definitions
|
|
15
|
+
==============
|
|
16
|
+
|
|
17
|
+
1.1 "Software" refers to NocoBase, consisting of Core, Studio, and Commercial Plugins, including their source code, installation packages, images, and all their modifications, updates, and upgrades.
|
|
18
|
+
|
|
19
|
+
1.2 "Core" refers to NocoBase Core, the open source application foundation released by the Company, including the runtime, plugins, application templates, development standards, and development skills placed in the same code repository.
|
|
20
|
+
|
|
21
|
+
1.3 "Studio" refers to NocoBase Studio, the governance platform for people and agents building software together, provided in the Commercial Edition.
|
|
22
|
+
|
|
23
|
+
1.4 "Commercial Plugins" refers to the plugins provided in the Commercial Edition that extend the capabilities of Core, such as advanced approvals and enterprise SSO.
|
|
24
|
+
|
|
25
|
+
1.5 "Community Edition" refers to the free version of the Software provided to the User through public channels, namely Core.
|
|
26
|
+
|
|
27
|
+
1.6 "Commercial Edition" refers to the paid version of the Software purchased by the User from the Company or its agents, downloaded through exclusive channels, and includes additional benefits. In addition to Core, it includes Studio and Commercial Plugins, and consists of two versions: Professional Edition and Enterprise Edition.
|
|
28
|
+
|
|
29
|
+
1.7 "Application" refers to a specific business use case application serving internal or external customers of the User, developed based on the Software, such as ERP/CRM. Studio is a development tool and is not part of any Application.
|
|
30
|
+
|
|
31
|
+
1.8 "Customer" refers to the clients who commission the User to develop Applications or purchase the User's Applications.
|
|
32
|
+
|
|
33
|
+
1.9 "Agent" refers to an AI agent that works with the Software, including the coding agents the User uses to develop Applications and the business agents in Applications.
|
|
34
|
+
|
|
35
|
+
1.10 "Third-Party Services" refers to the large models, coding agents, cloud services, and other third-party products and services that the User chooses to use with the Software.
|
|
36
|
+
|
|
37
|
+
1.11 "Third-Party Open Source Software" refers to open source software provided with the Software. They are licensed through various published open source software licenses or copyright notices accompanying such software.
|
|
38
|
+
|
|
39
|
+
===================================
|
|
40
|
+
2. Intellectual Property Protection
|
|
41
|
+
===================================
|
|
42
|
+
|
|
43
|
+
Except for Third-Party Open Source Software, the Company owns all copyrights, trademark rights, patent rights, trade secrets, and other intellectual property rights of the Software, and has registered and protected them in relevant countries and regions according to the "Paris Convention" or "TRIPS Agreement", ensuring that the intellectual property rights of the Software are internationally recognized and protected.
|
|
44
|
+
|
|
45
|
+
The Company claims no intellectual property rights in the business code written by the User or by Agents when developing Applications, or in the User's business data, skills, and knowledge bases. The parts of the Software contained in an Application remain the intellectual property of the Company and are subject to this Agreement.
|
|
46
|
+
|
|
47
|
+
=============
|
|
48
|
+
3. Disclaimer
|
|
49
|
+
=============
|
|
50
|
+
|
|
51
|
+
3.1 The User shall not use the Software to engage in activities that contravene applicable laws and regulations or offend against public order or religious prohibitions. All legal liabilities and consequences arising from the User's use shall be borne by the User.
|
|
52
|
+
|
|
53
|
+
3.2 The Company shall not be liable for any direct, indirect, special, incidental, or consequential damages (including but not limited to loss of profits, business interruption, data loss, or business information disclosure) caused by the User's use of the Software, even if it has been previously informed of the possibility of such damages.
|
|
54
|
+
|
|
55
|
+
3.3 Code written, content generated, and actions taken by Agents shall be reviewed and accepted by the User before being used in production. All consequences arising from the use of the work of Agents shall be borne by the User.
|
|
56
|
+
|
|
57
|
+
3.4 Third-Party Services are chosen and paid for by the User and are subject to their own terms. The Company shall not be liable for the availability, output, or data handling of Third-Party Services.
|
|
58
|
+
|
|
59
|
+
===============
|
|
60
|
+
4. License Type
|
|
61
|
+
===============
|
|
62
|
+
|
|
63
|
+
4.1 This Agreement serves as the unified license agreement for NocoBase Software, applying to both the Community Edition and the Commercial Editions.
|
|
64
|
+
|
|
65
|
+
4.2 Core is open source software. This Agreement incorporates and references the full text of the Apache License, Version 2.0 ("Apache-2.0", available at: https://www.apache.org/licenses/LICENSE-2.0 ). Users of Core must comply with the Apache-2.0 License as well as the supplementary terms set forth in this Agreement. In case of any inconsistency between Apache-2.0 and this Agreement, the supplementary terms of this Agreement shall prevail.
|
|
66
|
+
|
|
67
|
+
4.3 Studio and Commercial Plugins are commercial software of the Company and are not licensed under Apache-2.0. They can only be used under the Commercial License and in accordance with this Agreement.
|
|
68
|
+
|
|
69
|
+
================================================
|
|
70
|
+
5. Rights and Obligations of Open Source License
|
|
71
|
+
================================================
|
|
72
|
+
|
|
73
|
+
5.1 The Community Edition can be used for commercial purposes, including developing Applications for Customers.
|
|
74
|
+
|
|
75
|
+
5.2 It is not allowed to remove or change the NocoBase statement "AI + NocoBase" and "Build reliable business systems", or its link to https://www.nocobase.com/, at the bottom left of the interface of any Application, including Applications delivered to Customers, even though the code that displays the statement is part of the Application. If the default layout is replaced, the statement shall be kept at an equally visible position on the main interface of the Application. Pages that can be accessed without signing in and pages embedded in other systems are not subject to this requirement. When the sidebar is collapsed, showing the statement as an icon with a tooltip, as provided by default, meets this requirement. The main LOGO in the upper left corner of the page, the version number, and other information on the interface can be changed.
|
|
76
|
+
|
|
77
|
+
5.3 It is not allowed to remove or change all intellectual property statements about NocoBase in the code.
|
|
78
|
+
|
|
79
|
+
5.4 It is not allowed to provide to the public any form of AI application development platform or agent development platform SaaS/PaaS products using the original or modified Software.
|
|
80
|
+
|
|
81
|
+
===============================
|
|
82
|
+
6. Rights of Commercial License
|
|
83
|
+
===============================
|
|
84
|
+
|
|
85
|
+
6.1 Obtain a permanent commercial license of the Software.
|
|
86
|
+
|
|
87
|
+
6.2 Can develop Applications for Customers, deploy the completed Applications, including the Commercial Plugins they use, on Customers' servers, or host them and provide them to Customers as a service. This does not include Studio.
|
|
88
|
+
|
|
89
|
+
6.3 Customers can use the Commercial Plugins in the Applications delivered to them, within those Applications only, and can continue to develop those Applications on their own or through others.
|
|
90
|
+
|
|
91
|
+
6.4 Get software upgrades and exclusive technical support during the upgrade validity period. Upgrades and technical support are provided to the User only, and the User can upgrade the Commercial Plugins in Applications delivered to Customers during this period.
|
|
92
|
+
|
|
93
|
+
6.5 The licensed Software can be used for commercial purposes with no restrictions on the number of Applications, Customers, users, and Agents.
|
|
94
|
+
|
|
95
|
+
6.6 Can remove or change the NocoBase statement described in 5.2, and the brand, name, link, and other information about NocoBase on the interface of Applications.
|
|
96
|
+
|
|
97
|
+
6.7 If there are other agreements in the contract for the above rights, the contract agreement shall prevail.
|
|
98
|
+
|
|
99
|
+
====================================
|
|
100
|
+
7. Obligations of Commercial License
|
|
101
|
+
====================================
|
|
102
|
+
|
|
103
|
+
7.1 It is not allowed to remove or change all intellectual property statements about NocoBase in the code.
|
|
104
|
+
|
|
105
|
+
7.2 It is not allowed to sell, transfer, lease, share, gift, or distribute the Commercial License. Deploying Applications for Customers under 6.2 is not considered sharing the Commercial License, but the license key, access token, or other credentials of the Commercial License shall not be provided to Customers for any purpose other than deploying and running the Applications delivered to them.
|
|
106
|
+
|
|
107
|
+
7.3 It is not allowed to sell, transfer, lease, share, or distribute any form of AI application development platform, agent development platform, or developer tools developed based on Software.
|
|
108
|
+
|
|
109
|
+
7.4 It is not allowed to provide any form of AI application development platform or agent development platform SaaS/PaaS products to the public using the original or modified Software.
|
|
110
|
+
|
|
111
|
+
7.5 Studio can only be used by the User holding the Commercial License, on servers owned or rented by the User. It is not allowed to deploy Studio for Customers, or to provide Studio to Customers or any third party in any form, including as a hosted service. Customers that need Studio shall purchase their own Commercial License.
|
|
112
|
+
|
|
113
|
+
7.6 Customer personnel can take part in requirement confirmation, acceptance, and release approval in the User's Studio for the Applications the User develops for them. This is not considered providing Studio to Customers, but it is not allowed to let Customers use Studio to develop their own projects.
|
|
114
|
+
|
|
115
|
+
7.7 If there is a violation of the above obligations or the terms of this Agreement, the rights owned by the User will be immediately terminated, the paid fees will not be refunded, and the Company reserves the right to pursue the User's legal responsibility.
|
|
116
|
+
|
|
117
|
+
7.8 If there are other agreements in the contract for the above obligations, the contract agreement shall prevail.
|
|
118
|
+
|
|
119
|
+
=============================================================
|
|
120
|
+
8. Legal Jurisdiction, Interpretation, and Dispute Resolution
|
|
121
|
+
=============================================================
|
|
122
|
+
|
|
123
|
+
8.1 Except for Mainland China, the interpretation, application, and all matters related to this agreement are subject to the jurisdiction of Singapore law.
|
|
124
|
+
|
|
125
|
+
8.2 Any dispute related to this Agreement should first be resolved through friendly negotiation. If the negotiation fails to resolve the dispute, the dispute should be submitted to the International Chamber of Commerce (ICC) for arbitration. The arbitration venue should be Singapore, conducted in English.
|
|
126
|
+
|
|
127
|
+
8.3 All terms and conditions of this Agreement shall be deemed enforceable to the maximum extent permitted by applicable law. If any term of this Agreement is deemed invalid by any applicable law, the invalidity of that term does not affect the validity of any other term of this Agreement, and it should be deemed that the invalid term has been modified as much as possible to make it valid and enforceable, or if the term cannot be modified, it should be deemed to have been deleted from this Agreement.
|
|
128
|
+
|
|
129
|
+
8.4 The arbitration award is final, binding on both parties, and can be enforced in any court with jurisdiction.
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { useTranslation } from '@nocobase/i18n/client';
|
|
2
2
|
import { ArrowLeftIcon } from 'lucide-react';
|
|
3
3
|
import type { ReactElement, ReactNode } from 'react';
|
|
4
|
-
import { Link, type To, useResolvedPath } from 'react-router';
|
|
4
|
+
import { Link, type To, parsePath, useResolvedPath } from 'react-router';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { navigationDestination } from '../routing/navigation-destination.js';
|
|
7
|
+
import {
|
|
8
|
+
useRouteTree,
|
|
9
|
+
useRouteAccess,
|
|
10
|
+
useRouteSearches,
|
|
11
|
+
} from '../routing/route-context.js';
|
|
7
12
|
|
|
8
13
|
import { cn } from 'cn';
|
|
9
14
|
|
|
10
15
|
export interface BackButtonProps {
|
|
11
16
|
/**
|
|
12
17
|
* Where it leads. Defaults to the parent route with its last visited query; a directly opened detail falls back
|
|
13
|
-
* to the default parent list. An explicit destination
|
|
18
|
+
* to the default parent list. An explicit destination must match an authorized application page.
|
|
14
19
|
*/
|
|
15
20
|
readonly to?: To;
|
|
16
21
|
/** The label, "Back" by default. */
|
|
@@ -31,11 +36,36 @@ export function BackButton({
|
|
|
31
36
|
children,
|
|
32
37
|
className,
|
|
33
38
|
to,
|
|
34
|
-
}: BackButtonProps = {}): ReactElement {
|
|
39
|
+
}: BackButtonProps = {}): ReactElement | null {
|
|
35
40
|
const { t } = useTranslation();
|
|
36
41
|
const parent = useResolvedPath('..');
|
|
42
|
+
const resolved = useResolvedPath(to ?? '..');
|
|
43
|
+
const routes = useRouteTree();
|
|
44
|
+
const access = useRouteAccess();
|
|
37
45
|
const searches = useRouteSearches();
|
|
38
46
|
|
|
47
|
+
const fallback = {
|
|
48
|
+
...parent,
|
|
49
|
+
search: searches.get(parent.pathname.replace(/\/+$/u, '') || '/') ?? '',
|
|
50
|
+
};
|
|
51
|
+
const rawPath =
|
|
52
|
+
typeof to === 'string' ? parsePath(to).pathname : to?.pathname;
|
|
53
|
+
const unsafe =
|
|
54
|
+
rawPath &&
|
|
55
|
+
(/^(?:[a-z][a-z\d+.-]*:|\/\/)/iu.test(rawPath) ||
|
|
56
|
+
rawPath.includes('\\') ||
|
|
57
|
+
[...rawPath].some((character) => character.charCodeAt(0) <= 32));
|
|
58
|
+
const primary = unsafe
|
|
59
|
+
? undefined
|
|
60
|
+
: navigationDestination(
|
|
61
|
+
to && rawPath?.startsWith('/') ? to : to ? resolved : fallback,
|
|
62
|
+
routes,
|
|
63
|
+
access,
|
|
64
|
+
);
|
|
65
|
+
const destination =
|
|
66
|
+
primary ?? navigationDestination(fallback, routes, access);
|
|
67
|
+
if (!destination) return null;
|
|
68
|
+
|
|
39
69
|
return (
|
|
40
70
|
<Link
|
|
41
71
|
replace
|
|
@@ -43,16 +73,11 @@ export function BackButton({
|
|
|
43
73
|
'inline-flex w-fit items-center gap-1 text-sm text-muted-foreground transition-colors hover:text-foreground',
|
|
44
74
|
className,
|
|
45
75
|
)}
|
|
46
|
-
to={
|
|
47
|
-
to ?? {
|
|
48
|
-
pathname: '..',
|
|
49
|
-
search:
|
|
50
|
-
searches.get(parent.pathname.replace(/\/+$/u, '') || '/') ?? '',
|
|
51
|
-
}
|
|
52
|
-
}
|
|
76
|
+
to={destination}
|
|
53
77
|
>
|
|
54
78
|
<ArrowLeftIcon className='size-4' />
|
|
55
|
-
{
|
|
79
|
+
{(primary ? children : undefined) ??
|
|
80
|
+
t('navigation.back', { defaultValue: 'Back' })}
|
|
56
81
|
</Link>
|
|
57
82
|
);
|
|
58
83
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { ComponentPropsWithoutRef, ReactElement } from 'react';
|
|
2
2
|
import { cn } from 'cn';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
BRAND_MARK_PATH,
|
|
6
|
+
BRAND_MARK_TRANSLATE,
|
|
7
|
+
} from './brand-mark-geometry.js';
|
|
5
8
|
|
|
6
9
|
/** Keep the brand recognizable; the orbit, rather than the bricks, conveys progress. */
|
|
7
10
|
export function BrandLoadingMark({
|
|
@@ -25,7 +28,7 @@ export function BrandLoadingMark({
|
|
|
25
28
|
strokeWidth='2'
|
|
26
29
|
opacity='.12'
|
|
27
30
|
/>
|
|
28
|
-
<g className='
|
|
31
|
+
<g className='origin-center motion-safe:animate-spin'>
|
|
29
32
|
<path
|
|
30
33
|
d='M32 4a28 28 0 0 1 28 28'
|
|
31
34
|
stroke='currentColor'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Official Logomark vector from the supplied logos/Transparent/Vectors asset.
|
|
2
2
|
* Keep geometry unchanged; color, size and decorative animation belong to consumers.
|
|
3
3
|
*/
|
|
4
|
-
export const BRAND_MARK_VIEW_BOX = '0 0 100 87.19720458984375';
|
|
5
|
-
export const BRAND_MARK_TRANSLATE = 'translate(0 -6.401886463165283)';
|
|
6
|
-
export const BRAND_MARK_PATH =
|
|
4
|
+
export const BRAND_MARK_VIEW_BOX: string = '0 0 100 87.19720458984375';
|
|
5
|
+
export const BRAND_MARK_TRANSLATE: string = 'translate(0 -6.401886463165283)';
|
|
6
|
+
export const BRAND_MARK_PATH: string =
|
|
7
7
|
'M99.876 55.369c-.025-.045-.076-.072-.109-.111-.066-.078-.125-.168-.217-.221l-.014-.008-.001-.002h-.001L80.368 43.648v-24.67a.92.92 0 0 0-.124-.459c-.026-.045-.075-.072-.108-.111-.066-.08-.126-.168-.218-.221l-.014-.008-.001-.002h-.002L60.286 6.531a.92.92 0 0 0-.94 0L20.099 29.834h-.002l-.001.002-.014.006c-.092.055-.151.143-.218.221-.033.041-.082.068-.108.113a.907.907 0 0 0-.124.457v1.361L.466 43.373H.465l-.001.002c-.098.055-.162.143-.23.227-.034.041-.084.068-.109.113a.898.898 0 0 0-.125.457v25.195c0 .324.171.625.45.791l39.264 23.311c.002.002.006 0 .008.002l.007.008a.924.924 0 0 0 .911 0c.003-.002.004-.006.008-.008.001-.002.005 0 .007-.002l19.161-11.377 19.162 11.377c.002.002.006 0 .008.002.004.002.004.006.007.008a.924.924 0 0 0 .911 0c.003-.002.004-.006.008-.008.002-.002.005 0 .007-.002L99.55 81.812a.916.916 0 0 0 .45-.791V55.828a.92.92 0 0 0-.124-.459zM41.104 65.867V42.812l37.424-22.219v23.055L41.104 65.867zM59.815 8.391l17.83 10.588L59.82 29.561 40.185 41.219l-17.83-10.586 37.46-22.242zM39.264 42.812V65.866L21.472 55.305V32.248l17.792 10.564z';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { usePageBreadcrumbLevels } from '@nocobase/app-client';
|
|
2
2
|
import type { PageBreadcrumbLevel } from '@nocobase/app-client';
|
|
3
3
|
import { useTranslation } from '@nocobase/i18n/client';
|
|
4
|
+
import { ArrowLeftIcon } from 'lucide-react';
|
|
4
5
|
import { Fragment, type ReactElement } from 'react';
|
|
5
6
|
import { Link } from 'react-router';
|
|
6
7
|
|
|
@@ -23,10 +24,13 @@ import { settingsHomeRoute } from '#lib/navigation-transform';
|
|
|
23
24
|
import { cn } from 'cn';
|
|
24
25
|
|
|
25
26
|
import {
|
|
27
|
+
useRouteTree,
|
|
28
|
+
useRouteAccess,
|
|
26
29
|
useRouteTrail,
|
|
27
30
|
useRouteSearches,
|
|
28
31
|
type RouteTrailEntry,
|
|
29
32
|
} from '../routing/route-context.js';
|
|
33
|
+
import { navigationDestination } from '../routing/navigation-destination.js';
|
|
30
34
|
import { routeKey } from '../routing/route-navigation.js';
|
|
31
35
|
|
|
32
36
|
export interface BreadcrumbsProps {
|
|
@@ -45,7 +49,7 @@ export interface BreadcrumbsProps {
|
|
|
45
49
|
* `usePageBreadcrumb` from `@nocobase/app-client`. Earlier levels link where a page sits behind them; the last is the
|
|
46
50
|
* current page.
|
|
47
51
|
*
|
|
48
|
-
* On a phone
|
|
52
|
+
* On a phone the current title and a link to the nearest authorized parent show; on a medium screen a trail of three or more levels keeps its
|
|
49
53
|
* first and last and folds the middle into a menu.
|
|
50
54
|
*/
|
|
51
55
|
export function Breadcrumbs({
|
|
@@ -53,10 +57,18 @@ export function Breadcrumbs({
|
|
|
53
57
|
denied,
|
|
54
58
|
}: BreadcrumbsProps = {}): ReactElement | null {
|
|
55
59
|
const trail = useRouteTrail();
|
|
60
|
+
const routes = useRouteTree();
|
|
61
|
+
const contextAccess = useRouteAccess();
|
|
62
|
+
const access = denied
|
|
63
|
+
? {
|
|
64
|
+
...contextAccess,
|
|
65
|
+
denied: new Set([...contextAccess.denied, ...denied]),
|
|
66
|
+
}
|
|
67
|
+
: contextAccess;
|
|
56
68
|
const searches = useRouteSearches();
|
|
57
69
|
const declared = usePageBreadcrumbLevels();
|
|
58
70
|
const { t } = useTranslation();
|
|
59
|
-
const
|
|
71
|
+
const candidates =
|
|
60
72
|
declared ??
|
|
61
73
|
trail.flatMap((entry): PageBreadcrumbLevel[] => {
|
|
62
74
|
const title = routeLevelTitle(entry);
|
|
@@ -93,6 +105,19 @@ export function Breadcrumbs({
|
|
|
93
105
|
];
|
|
94
106
|
});
|
|
95
107
|
|
|
108
|
+
const trailLevels = candidates.flatMap(
|
|
109
|
+
(level, index): PageBreadcrumbLevel[] => {
|
|
110
|
+
if (!level.to) return [level];
|
|
111
|
+
const to = navigationDestination(level.to, routes, access);
|
|
112
|
+
// Keep the current title during permission checks, but never expose an inaccessible ancestor's name.
|
|
113
|
+
return to
|
|
114
|
+
? [{ ...level, to }]
|
|
115
|
+
: index === candidates.length - 1
|
|
116
|
+
? [{ label: level.label }]
|
|
117
|
+
: [];
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
|
|
96
121
|
if (trailLevels.length === 0) return null;
|
|
97
122
|
// A level is told apart by its depth and label; two levels may share either alone.
|
|
98
123
|
const levels = trailLevels.map((level, depth) => ({
|
|
@@ -101,6 +126,7 @@ export function Breadcrumbs({
|
|
|
101
126
|
}));
|
|
102
127
|
const last = levels.length - 1;
|
|
103
128
|
const folded = levels.slice(1, last);
|
|
129
|
+
const parent = levels.slice(0, last).findLast((level) => level.to);
|
|
104
130
|
|
|
105
131
|
return (
|
|
106
132
|
<Breadcrumb
|
|
@@ -108,6 +134,21 @@ export function Breadcrumbs({
|
|
|
108
134
|
className={cn('min-w-0', className)}
|
|
109
135
|
>
|
|
110
136
|
<BreadcrumbList className='flex-nowrap'>
|
|
137
|
+
{parent ? (
|
|
138
|
+
<BreadcrumbItem className='shrink-0 md:hidden'>
|
|
139
|
+
<BreadcrumbLink
|
|
140
|
+
aria-label={t('navigation.backTo', {
|
|
141
|
+
label: parent.label,
|
|
142
|
+
defaultValue: `Back to ${parent.label}`,
|
|
143
|
+
})}
|
|
144
|
+
title={parent.label}
|
|
145
|
+
className='inline-flex size-9 items-center justify-center rounded-sm focus-visible:outline-2 focus-visible:outline-ring'
|
|
146
|
+
render={<Link to={parent.to!} replace />}
|
|
147
|
+
>
|
|
148
|
+
<ArrowLeftIcon aria-hidden='true' className='size-4' />
|
|
149
|
+
</BreadcrumbLink>
|
|
150
|
+
</BreadcrumbItem>
|
|
151
|
+
) : null}
|
|
111
152
|
{levels.map((level, index) => (
|
|
112
153
|
<Fragment key={level.key}>
|
|
113
154
|
{index === 1 && folded.length > 0 ? (
|
|
@@ -154,7 +195,10 @@ export function Breadcrumbs({
|
|
|
154
195
|
)}
|
|
155
196
|
>
|
|
156
197
|
{index === last ? (
|
|
157
|
-
<BreadcrumbPage
|
|
198
|
+
<BreadcrumbPage
|
|
199
|
+
title={level.label}
|
|
200
|
+
className='truncate font-medium'
|
|
201
|
+
>
|
|
158
202
|
{level.label}
|
|
159
203
|
</BreadcrumbPage>
|
|
160
204
|
) : level.to ? (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ListState } from '@nocobase/app-client';
|
|
2
2
|
import { ListFeedback, ListFeedbackActions } from '#components/list-feedback';
|
|
3
|
+
import { RefreshIndicator } from '#components/loading';
|
|
3
4
|
import {
|
|
4
5
|
type ColumnDef,
|
|
5
6
|
type PaginationState,
|
|
@@ -17,7 +18,14 @@ import {
|
|
|
17
18
|
getSortedRowModel,
|
|
18
19
|
useReactTable,
|
|
19
20
|
} from '@tanstack/react-table';
|
|
20
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
type ReactElement,
|
|
23
|
+
type ReactNode,
|
|
24
|
+
useEffect,
|
|
25
|
+
useMemo,
|
|
26
|
+
useRef,
|
|
27
|
+
useState,
|
|
28
|
+
} from 'react';
|
|
21
29
|
|
|
22
30
|
import { DataTablePagination } from './pagination.js';
|
|
23
31
|
import {
|
|
@@ -41,7 +49,18 @@ export interface DataTableProps<TData, TValue = unknown> {
|
|
|
41
49
|
readonly sortingState?: SortingState;
|
|
42
50
|
readonly onSortingChange?: OnChangeFn<SortingState>;
|
|
43
51
|
readonly showSelectedCount?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* The current request is in flight. With rows of the current query and permission scope this is a background
|
|
54
|
+
* refresh: rows stay and a delayed refresh indicator appears. Without them the body shows the loading state.
|
|
55
|
+
* Explicit false pauses loading feedback for an unresolved, inactive query; omitting it uses listState.
|
|
56
|
+
*/
|
|
44
57
|
readonly loading?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* The current query's stable identity, such as its request path. When it
|
|
60
|
+
* changes the loading and refresh feedback restart their display delay;
|
|
61
|
+
* the table, its rows' geometry and its controls stay mounted.
|
|
62
|
+
*/
|
|
63
|
+
readonly loadingIdentity?: unknown;
|
|
45
64
|
readonly listState?: ListState;
|
|
46
65
|
readonly onRetry?: () => void;
|
|
47
66
|
readonly onClearFilters?: () => void;
|
|
@@ -95,7 +114,8 @@ export function DataTable<TData, TValue = unknown>({
|
|
|
95
114
|
sortingState,
|
|
96
115
|
onSortingChange,
|
|
97
116
|
showSelectedCount = true,
|
|
98
|
-
loading
|
|
117
|
+
loading,
|
|
118
|
+
loadingIdentity,
|
|
99
119
|
listState,
|
|
100
120
|
onRetry,
|
|
101
121
|
onClearFilters,
|
|
@@ -176,6 +196,37 @@ export function DataTable<TData, TValue = unknown>({
|
|
|
176
196
|
rows.length > 0 && (state === 'ready' || state === 'refresh-failed');
|
|
177
197
|
const showFeedback = !showRows || state === 'refresh-failed';
|
|
178
198
|
const feedbackColumnCount = Math.max(1, table.getVisibleLeafColumns().length);
|
|
199
|
+
const showPagination =
|
|
200
|
+
pagination &&
|
|
201
|
+
(state === 'ready' || (state === 'refresh-failed' && rows.length > 0));
|
|
202
|
+
// A new query hides the previous rows at once; keep their body and footer
|
|
203
|
+
// geometry while it loads so the page below does not jump.
|
|
204
|
+
const bodyRef = useRef<HTMLTableSectionElement>(null);
|
|
205
|
+
const footerRef = useRef<HTMLDivElement>(null);
|
|
206
|
+
const [reserved, setReserved] = useState<{
|
|
207
|
+
readonly body: number;
|
|
208
|
+
readonly footer: number;
|
|
209
|
+
}>();
|
|
210
|
+
if (!showRows && state !== 'loading' && reserved !== undefined)
|
|
211
|
+
setReserved(undefined);
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
const body = bodyRef.current;
|
|
214
|
+
if (!showRows || !body || typeof ResizeObserver === 'undefined') return;
|
|
215
|
+
const footer = footerRef.current;
|
|
216
|
+
const observer = new ResizeObserver(() => {
|
|
217
|
+
const next = {
|
|
218
|
+
body: body.offsetHeight,
|
|
219
|
+
footer: footer?.offsetHeight ?? 0,
|
|
220
|
+
};
|
|
221
|
+
setReserved((old) =>
|
|
222
|
+
old?.body === next.body && old.footer === next.footer ? old : next,
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
observer.observe(body);
|
|
226
|
+
if (footer) observer.observe(footer);
|
|
227
|
+
return () => observer.disconnect();
|
|
228
|
+
}, [showRows, showPagination]);
|
|
229
|
+
const reserving = state === 'loading' && reserved !== undefined;
|
|
179
230
|
|
|
180
231
|
return (
|
|
181
232
|
<div className={cn('flex flex-col gap-4', className)}>
|
|
@@ -194,8 +245,14 @@ export function DataTable<TData, TValue = unknown>({
|
|
|
194
245
|
/>
|
|
195
246
|
</div>
|
|
196
247
|
) : null}
|
|
197
|
-
<div className='overflow-hidden rounded-lg border'>
|
|
198
|
-
|
|
248
|
+
<div className='relative overflow-hidden rounded-lg border'>
|
|
249
|
+
{/* A retry after a refresh failure is announced by its own toolbar button. */}
|
|
250
|
+
<RefreshIndicator
|
|
251
|
+
pending={Boolean(loading && showRows && state !== 'refresh-failed')}
|
|
252
|
+
identity={loadingIdentity}
|
|
253
|
+
/>
|
|
254
|
+
{/* Only retained rows are busy; a loading body owns its own busy region and status. */}
|
|
255
|
+
<Table aria-busy={loading && showRows}>
|
|
199
256
|
<TableHeader>
|
|
200
257
|
{table.getHeaderGroups().map((headerGroup) => (
|
|
201
258
|
<TableRow key={headerGroup.id}>
|
|
@@ -224,15 +281,27 @@ export function DataTable<TData, TValue = unknown>({
|
|
|
224
281
|
</TableRow>
|
|
225
282
|
))}
|
|
226
283
|
</TableHeader>
|
|
227
|
-
<TableBody>
|
|
284
|
+
<TableBody ref={bodyRef}>
|
|
228
285
|
{showFeedback ? (
|
|
229
286
|
<TableRow>
|
|
230
|
-
<TableCell
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
287
|
+
<TableCell
|
|
288
|
+
colSpan={feedbackColumnCount}
|
|
289
|
+
className='p-0'
|
|
290
|
+
style={
|
|
291
|
+
reserving && reserved.body > 0
|
|
292
|
+
? { height: reserved.body }
|
|
293
|
+
: undefined
|
|
294
|
+
}
|
|
295
|
+
>
|
|
296
|
+
{/* Readiness may stay unresolved while the parent query is paused. */}
|
|
297
|
+
{state !== 'loading' || loading !== false ? (
|
|
298
|
+
<ListFeedback
|
|
299
|
+
state={state}
|
|
300
|
+
identity={loadingIdentity}
|
|
301
|
+
empty={emptyMessage}
|
|
302
|
+
emptyAction={emptyAction}
|
|
303
|
+
/>
|
|
304
|
+
) : null}
|
|
236
305
|
</TableCell>
|
|
237
306
|
</TableRow>
|
|
238
307
|
) : null}
|
|
@@ -258,14 +327,16 @@ export function DataTable<TData, TValue = unknown>({
|
|
|
258
327
|
</TableBody>
|
|
259
328
|
</Table>
|
|
260
329
|
</div>
|
|
261
|
-
{pagination &&
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
330
|
+
{showPagination || (pagination && reserving) ? (
|
|
331
|
+
<div ref={footerRef} data-slot='data-table-footer'>
|
|
332
|
+
<DataTablePagination
|
|
333
|
+
table={table}
|
|
334
|
+
pageSizeOptions={pageSizeOptions}
|
|
335
|
+
showSelectedCount={showSelectedCount}
|
|
336
|
+
disabled={loading || !showPagination}
|
|
337
|
+
countPending={!showPagination}
|
|
338
|
+
/>
|
|
339
|
+
</div>
|
|
269
340
|
) : null}
|
|
270
341
|
</div>
|
|
271
342
|
);
|