@alfakit/react 0.1.2 → 0.1.6
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/CHANGELOG.md +8 -0
- package/dist/components/actions/action-modals.js +4 -0
- package/dist/components/actions/action-modals.js.map +1 -1
- package/dist/components/ai/copilot.d.ts +3 -1
- package/dist/components/ai/copilot.d.ts.map +1 -1
- package/dist/components/ai/copilot.js +4 -1
- package/dist/components/ai/copilot.js.map +1 -1
- package/dist/components/cms/live-preview.js +3 -2
- package/dist/components/cms/live-preview.js.map +1 -1
- package/dist/components/cms/page.js +1 -1
- package/dist/components/fields/builder.js +15 -6
- package/dist/components/fields/builder.js.map +1 -1
- package/dist/components/fields/image-editor-dialog.js +4 -2
- package/dist/components/fields/image-editor-dialog.js.map +1 -1
- package/dist/components/fields/item-actions.js +10 -10
- package/dist/components/fields/item-actions.js.map +1 -1
- package/dist/components/fields/key-value.d.ts.map +1 -1
- package/dist/components/fields/key-value.js +3 -3
- package/dist/components/fields/key-value.js.map +1 -1
- package/dist/components/fields/modal-table-select.js +6 -5
- package/dist/components/fields/modal-table-select.js.map +1 -1
- package/dist/components/fields/repeater.js +2 -2
- package/dist/components/fields/repeater.js.map +1 -1
- package/dist/components/fields/rich-editor.js +2 -1
- package/dist/components/fields/rich-editor.js.map +1 -1
- package/dist/components/form/form-block.js +1 -1
- package/dist/components/form/form-block.js.map +1 -1
- package/dist/components/form/form-context.js +2 -2
- package/dist/components/form/form-context.js.map +1 -1
- package/dist/components/kanban/kanban.js +4 -3
- package/dist/components/kanban/kanban.js.map +1 -1
- package/dist/components/layout/section.js +2 -2
- package/dist/components/layout/section.js.map +1 -1
- package/dist/components/panel/app-sidebar.d.ts +2 -3
- package/dist/components/panel/app-sidebar.d.ts.map +1 -1
- package/dist/components/panel/app-sidebar.js +1 -24
- package/dist/components/panel/app-sidebar.js.map +1 -1
- package/dist/components/panel/brand.d.ts +10 -0
- package/dist/components/panel/brand.d.ts.map +1 -0
- package/dist/components/panel/brand.js +37 -0
- package/dist/components/panel/brand.js.map +1 -0
- package/dist/components/panel/index.d.ts +2 -1
- package/dist/components/panel/topbar.js +1 -1
- package/dist/components/realtime/live-records-pill.d.ts.map +1 -1
- package/dist/components/realtime/live-records-pill.js +6 -3
- package/dist/components/realtime/live-records-pill.js.map +1 -1
- package/dist/components/table/cells.d.ts +6 -0
- package/dist/components/table/cells.d.ts.map +1 -1
- package/dist/components/table/cells.js +6 -2
- package/dist/components/table/cells.js.map +1 -1
- package/dist/components/table/column-manager.js +8 -7
- package/dist/components/table/column-manager.js.map +1 -1
- package/dist/components/table/content-layout.js +2 -1
- package/dist/components/table/content-layout.js.map +1 -1
- package/dist/components/table/pagination.js +16 -13
- package/dist/components/table/pagination.js.map +1 -1
- package/dist/components/table/table-block.js +14 -4
- package/dist/components/table/table-block.js.map +1 -1
- package/dist/components/ui/dialog.d.ts.map +1 -1
- package/dist/components/ui/dialog.js +3 -1
- package/dist/components/ui/dialog.js.map +1 -1
- package/dist/components/ui/popover.d.ts.map +1 -1
- package/dist/components/ui/popover.js +3 -1
- package/dist/components/ui/popover.js.map +1 -1
- package/dist/components/ui/sheet.d.ts.map +1 -1
- package/dist/components/ui/sheet.js +3 -1
- package/dist/components/ui/sheet.js.map +1 -1
- package/dist/components/ui/tooltip.js +3 -1
- package/dist/components/ui/tooltip.js.map +1 -1
- package/dist/context.d.ts +5 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +5 -1
- package/dist/context.js.map +1 -1
- package/dist/create-alfakit.d.ts.map +1 -1
- package/dist/create-alfakit.js +8 -5
- package/dist/create-alfakit.js.map +1 -1
- package/dist/eject-manifest.json +16 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +24 -21
- package/dist/layouts/app-layout.d.ts.map +1 -1
- package/dist/layouts/app-layout.js +2 -29
- package/dist/layouts/app-layout.js.map +1 -1
- package/dist/layouts/auth-layout.js +2 -2
- package/dist/layouts/auth-layout.js.map +1 -1
- package/dist/layouts/fullscreen-layout.js +1 -1
- package/dist/layouts/fullscreen-layout.js.map +1 -1
- package/dist/layouts/lazy-layouts.js +15 -0
- package/dist/layouts/lazy-layouts.js.map +1 -0
- package/dist/layouts/max-widths.js +34 -0
- package/dist/layouts/max-widths.js.map +1 -0
- package/dist/layouts/panel-layouts.d.ts +10 -0
- package/dist/layouts/panel-layouts.d.ts.map +1 -0
- package/dist/layouts/panel-layouts.js +20 -0
- package/dist/layouts/panel-layouts.js.map +1 -0
- package/dist/layouts/top-nav-layout.js +2 -2
- package/dist/layouts/top-nav-layout.js.map +1 -1
- package/dist/pages/page.d.ts.map +1 -1
- package/dist/pages/page.js +12 -7
- package/dist/pages/page.js.map +1 -1
- package/dist/standalone/alfakit-component.js +11 -6
- package/dist/standalone/alfakit-component.js.map +1 -1
- package/dist/standalone.css +7 -0
- package/dist/starter-kit/dashboard-widgets.d.ts +33 -0
- package/dist/starter-kit/dashboard-widgets.d.ts.map +1 -0
- package/dist/starter-kit/dashboard-widgets.js +53 -0
- package/dist/starter-kit/dashboard-widgets.js.map +1 -0
- package/dist/starter-kit/impersonation-banner.js +34 -0
- package/dist/starter-kit/impersonation-banner.js.map +1 -0
- package/dist/starter-kit/index.d.ts +3 -2
- package/dist/starter-kit/index.js +3 -2
- package/dist/starter-kit/starter-kit.d.ts +6 -0
- package/dist/starter-kit/starter-kit.d.ts.map +1 -1
- package/dist/starter-kit/starter-kit.js +18 -7
- package/dist/starter-kit/starter-kit.js.map +1 -1
- package/dist/starter-kit/widgets-loaded.js +20 -4
- package/dist/starter-kit/widgets-loaded.js.map +1 -1
- package/dist/starter-kit/widgets-triggers.js +34 -3
- package/dist/starter-kit/widgets-triggers.js.map +1 -1
- package/dist/starter-kit/widgets.d.ts +13 -5
- package/dist/starter-kit/widgets.d.ts.map +1 -1
- package/dist/starter-kit/widgets.js +56 -18
- package/dist/starter-kit/widgets.js.map +1 -1
- package/dist/styles.css +15 -2
- package/dist/themes.css +6 -1
- package/package.json +2 -2
- package/src/components/actions/action-modals.tsx +10 -0
- package/src/components/ai/copilot.tsx +14 -1
- package/src/components/cms/live-preview.tsx +5 -2
- package/src/components/fields/builder.tsx +14 -5
- package/src/components/fields/image-editor-dialog.tsx +7 -2
- package/src/components/fields/item-actions.tsx +32 -9
- package/src/components/fields/key-value.tsx +3 -2
- package/src/components/fields/modal-table-select.tsx +8 -3
- package/src/components/fields/repeater.tsx +2 -3
- package/src/components/fields/rich-editor.tsx +3 -0
- package/src/components/form/form-block.tsx +2 -1
- package/src/components/form/form-context.tsx +2 -2
- package/src/components/kanban/kanban.tsx +16 -3
- package/src/components/layout/section.tsx +2 -2
- package/src/components/panel/app-sidebar.tsx +2 -38
- package/src/components/panel/brand.tsx +46 -0
- package/src/components/realtime/live-records-pill.tsx +4 -3
- package/src/components/table/cells.tsx +9 -2
- package/src/components/table/column-manager.tsx +12 -10
- package/src/components/table/content-layout.tsx +5 -1
- package/src/components/table/pagination.tsx +16 -9
- package/src/components/table/table-block.tsx +25 -12
- package/src/components/ui/dialog.tsx +5 -1
- package/src/components/ui/popover.tsx +5 -1
- package/src/components/ui/sheet.tsx +5 -1
- package/src/components/ui/tooltip.tsx +5 -1
- package/src/context.tsx +10 -0
- package/src/create-alfakit.tsx +11 -2
- package/src/index.ts +2 -0
- package/src/layouts/app-layout.tsx +4 -28
- package/src/layouts/auth-layout.tsx +2 -2
- package/src/layouts/fullscreen-layout.tsx +1 -1
- package/src/layouts/lazy-layouts.ts +14 -0
- package/src/layouts/max-widths.ts +29 -0
- package/src/layouts/panel-layouts.ts +17 -0
- package/src/layouts/top-nav-layout.tsx +2 -2
- package/src/pages/page.tsx +21 -12
- package/src/standalone/alfakit-component.tsx +18 -7
- package/src/starter-kit/dashboard-widgets.tsx +78 -0
- package/src/starter-kit/impersonation-banner.tsx +48 -0
- package/src/starter-kit/index.ts +2 -0
- package/src/starter-kit/starter-kit.tsx +31 -4
- package/src/starter-kit/widgets-loaded.tsx +22 -0
- package/src/starter-kit/widgets-triggers.tsx +36 -1
- package/src/starter-kit/widgets.tsx +95 -20
- package/src/styles.css +15 -2
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { lazyComponent } from "../lib/lazy.js";
|
|
2
|
-
import { NotificationsTrigger, SearchTrigger, TenantSwitcherTrigger } from "./widgets-triggers.js";
|
|
3
|
-
import { starterKitWidgets } from "@alfakit/core";
|
|
2
|
+
import { CopilotTrigger, NotificationsTrigger, SearchTrigger, TenantSwitcherTrigger } from "./widgets-triggers.js";
|
|
3
|
+
import { isAlfakitPage, starterKitWidgets } from "@alfakit/core";
|
|
4
4
|
import { usePage } from "@inertiajs/react";
|
|
5
|
-
import {
|
|
5
|
+
import { Suspense, lazy } from "react";
|
|
6
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
6
7
|
//#region src/starter-kit/widgets.tsx
|
|
7
8
|
const load = () => import("./widgets-loaded.js");
|
|
8
9
|
const LoadedSearch = lazyComponent(() => load().then((module) => module.LoadedSearch), (props) => /* @__PURE__ */ jsx(SearchTrigger, {
|
|
@@ -14,8 +15,26 @@ function preloadSearch() {
|
|
|
14
15
|
}
|
|
15
16
|
const LoadedNotifications = lazyComponent(() => load().then((module) => module.LoadedNotifications), (props) => /* @__PURE__ */ jsx(NotificationsTrigger, { ...props }));
|
|
16
17
|
const LoadedTenantSwitcher = lazyComponent(() => load().then((module) => module.LoadedTenantSwitcher), (props) => /* @__PURE__ */ jsx(TenantSwitcherTrigger, { ...props }));
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
const LoadedCopilot = lazyComponent(() => load().then((module) => module.LoadedCopilot), (props) => /* @__PURE__ */ jsx(CopilotTrigger, {
|
|
19
|
+
...props,
|
|
20
|
+
onLoad: preloadCopilot
|
|
21
|
+
}));
|
|
22
|
+
function preloadCopilot() {
|
|
23
|
+
LoadedCopilot.preload();
|
|
24
|
+
}
|
|
25
|
+
const LazyImpersonationBanner = lazy(() => import("./impersonation-banner.js"));
|
|
26
|
+
/** While impersonating, the first header component shows the banner on host pages. */
|
|
27
|
+
function StarterKitImpersonationBanner({ source }) {
|
|
28
|
+
const props = usePage().props;
|
|
29
|
+
const impersonation = source?.widgets.impersonation;
|
|
30
|
+
if (!impersonation || isAlfakitPage(props)) return null;
|
|
31
|
+
return /* @__PURE__ */ jsx(Suspense, {
|
|
32
|
+
fallback: null,
|
|
33
|
+
children: /* @__PURE__ */ jsx(LazyImpersonationBanner, { impersonation })
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function useWidgets(panelId, part) {
|
|
37
|
+
return starterKitWidgets(usePage().props, panelId, part);
|
|
19
38
|
}
|
|
20
39
|
/**
|
|
21
40
|
* AlfaKit's command palette and global search: the search field opens it, and so do the
|
|
@@ -23,13 +42,14 @@ function useWidgets(panelId) {
|
|
|
23
42
|
* nothing for guests or without global search and a command palette.
|
|
24
43
|
*/
|
|
25
44
|
function AlfakitSearch({ panel } = {}) {
|
|
26
|
-
const source = useWidgets(panel);
|
|
27
|
-
|
|
28
|
-
|
|
45
|
+
const source = useWidgets(panel, "search");
|
|
46
|
+
const banner = /* @__PURE__ */ jsx(StarterKitImpersonationBanner, { source });
|
|
47
|
+
if (!source?.widgets.search) return banner;
|
|
48
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [banner, /* @__PURE__ */ jsx("span", {
|
|
29
49
|
"data-slot": "starter-kit-search",
|
|
30
50
|
className: "inline-flex",
|
|
31
51
|
children: /* @__PURE__ */ jsx(LoadedSearch, { source })
|
|
32
|
-
});
|
|
52
|
+
})] });
|
|
33
53
|
}
|
|
34
54
|
/**
|
|
35
55
|
* The bell with the user's database notifications (`databaseNotifications()`), polling and
|
|
@@ -37,28 +57,46 @@ function AlfakitSearch({ panel } = {}) {
|
|
|
37
57
|
* notifications.
|
|
38
58
|
*/
|
|
39
59
|
function AlfakitNotifications({ panel } = {}) {
|
|
40
|
-
const source = useWidgets(panel);
|
|
41
|
-
|
|
42
|
-
|
|
60
|
+
const source = useWidgets(panel, "notifications");
|
|
61
|
+
const banner = /* @__PURE__ */ jsx(StarterKitImpersonationBanner, { source });
|
|
62
|
+
if (!source?.widgets.notifications) return banner;
|
|
63
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [banner, /* @__PURE__ */ jsx("span", {
|
|
43
64
|
"data-slot": "starter-kit-notifications",
|
|
44
65
|
className: "inline-flex",
|
|
45
66
|
children: /* @__PURE__ */ jsx(LoadedNotifications, { source })
|
|
46
|
-
});
|
|
67
|
+
})] });
|
|
47
68
|
}
|
|
48
69
|
/**
|
|
49
70
|
* The tenant switcher of a tenancy panel: the current tenant inside the panel, or a menu
|
|
50
71
|
* to choose one on the host's own pages. Renders nothing for guests or other panels.
|
|
51
72
|
*/
|
|
52
73
|
function AlfakitTenantSwitcher({ panel } = {}) {
|
|
53
|
-
const source = useWidgets(panel);
|
|
54
|
-
|
|
55
|
-
|
|
74
|
+
const source = useWidgets(panel, "tenancy");
|
|
75
|
+
const banner = /* @__PURE__ */ jsx(StarterKitImpersonationBanner, { source });
|
|
76
|
+
if (!source?.widgets.tenancy) return banner;
|
|
77
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [banner, /* @__PURE__ */ jsx("span", {
|
|
56
78
|
"data-slot": "starter-kit-tenant-switcher",
|
|
57
79
|
className: "inline-flex",
|
|
58
80
|
children: /* @__PURE__ */ jsx(LoadedTenantSwitcher, { source })
|
|
59
|
-
});
|
|
81
|
+
})] });
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The AI copilot of alfasix/alfakit-ai (`AiPlugin::copilot()`): its button and chat
|
|
85
|
+
* slide-over, opened with its key bindings (`mod+j`) from any page. Renders nothing for
|
|
86
|
+
* guests, without the plugin's copilot, for users who may not use it and (tenancy panels)
|
|
87
|
+
* outside a tenant.
|
|
88
|
+
*/
|
|
89
|
+
function AlfakitCopilot({ panel } = {}) {
|
|
90
|
+
const source = useWidgets(panel, "copilot");
|
|
91
|
+
const banner = /* @__PURE__ */ jsx(StarterKitImpersonationBanner, { source });
|
|
92
|
+
if (!source?.widgets.copilot) return banner;
|
|
93
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [banner, /* @__PURE__ */ jsx("span", {
|
|
94
|
+
"data-slot": "starter-kit-copilot",
|
|
95
|
+
className: "inline-flex",
|
|
96
|
+
children: /* @__PURE__ */ jsx(LoadedCopilot, { source })
|
|
97
|
+
})] });
|
|
60
98
|
}
|
|
61
99
|
//#endregion
|
|
62
|
-
export { AlfakitNotifications, AlfakitSearch, AlfakitTenantSwitcher };
|
|
100
|
+
export { AlfakitCopilot, AlfakitNotifications, AlfakitSearch, AlfakitTenantSwitcher };
|
|
63
101
|
|
|
64
102
|
//# sourceMappingURL=widgets.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widgets.js","names":[],"sources":["../../src/starter-kit/widgets.tsx"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"widgets.js","names":[],"sources":["../../src/starter-kit/widgets.tsx"],"sourcesContent":["import {\n isAlfakitPage,\n starterKitWidgets,\n type StarterKitWidgets,\n type StarterKitWidgetsSchema,\n} from '@alfakit/core'\nimport { usePage } from '@inertiajs/react'\nimport { lazy, Suspense } from 'react'\nimport { lazyComponent } from '../lib/lazy'\nimport {\n CopilotTrigger,\n NotificationsTrigger,\n SearchTrigger,\n TenantSwitcherTrigger,\n type WidgetProps,\n} from './widgets-triggers'\n\n/**\n * Drop-in header components of starter kit mode (`Panel::starterKit()`): AlfaKit's search,\n * notifications bell and tenant switcher in the host's own header, on every page of the\n * application. They read the shared `alfakitNavigation` prop, render a lightweight trigger\n * straight away, and load their code and the panel data the first time they render\n * (in the browser, never during SSR).\n *\n * ```tsx\n * import { AlfakitCopilot, AlfakitNotifications, AlfakitSearch } from '@alfakit/react'\n *\n * <AlfakitSearch />\n * <AlfakitNotifications />\n * <AlfakitCopilot />\n * ```\n */\nexport interface AlfakitHeaderWidgetProps {\n /** The `starterKit()` panel to use (by default the open AlfaKit page's panel, else the first one that has this component). */\n panel?: string\n}\n\nconst load = () => import('./widgets-loaded')\n\nconst LoadedSearch = lazyComponent<WidgetProps>(\n () => load().then((module) => module.LoadedSearch),\n (props) => <SearchTrigger {...props} onLoad={preloadSearch} />,\n)\n\nfunction preloadSearch() {\n void LoadedSearch.preload()\n}\n\nconst LoadedNotifications = lazyComponent<WidgetProps>(\n () => load().then((module) => module.LoadedNotifications),\n (props) => <NotificationsTrigger {...props} />,\n)\n\nconst LoadedTenantSwitcher = lazyComponent<WidgetProps>(\n () => load().then((module) => module.LoadedTenantSwitcher),\n (props) => <TenantSwitcherTrigger {...props} />,\n)\n\nconst LoadedCopilot = lazyComponent<WidgetProps>(\n () => load().then((module) => module.LoadedCopilot),\n (props) => <CopilotTrigger {...props} onLoad={preloadCopilot} />,\n)\n\nfunction preloadCopilot() {\n void LoadedCopilot.preload()\n}\n\n// Only needed while impersonating: its code loads then (and never on AlfaKit pages).\nconst LazyImpersonationBanner = lazy(() => import('./impersonation-banner'))\n\n/** While impersonating, the first header component shows the banner on host pages. */\nfunction StarterKitImpersonationBanner({ source }: { source: StarterKitWidgets | null }) {\n const props = usePage().props\n const impersonation = source?.widgets.impersonation\n\n if (!impersonation || isAlfakitPage(props)) return null\n\n return (\n <Suspense fallback={null}>\n <LazyImpersonationBanner impersonation={impersonation} />\n </Suspense>\n )\n}\n\nfunction useWidgets(\n panelId: string | undefined,\n part: keyof StarterKitWidgetsSchema,\n): StarterKitWidgets | null {\n return starterKitWidgets(usePage().props, panelId, part)\n}\n\n/**\n * AlfaKit's command palette and global search: the search field opens it, and so do the\n * panel's key bindings (⌘K / Ctrl+K by default), registered once for the page. Renders\n * nothing for guests or without global search and a command palette.\n */\nexport function AlfakitSearch({ panel }: AlfakitHeaderWidgetProps = {}) {\n const source = useWidgets(panel, 'search')\n\n const banner = <StarterKitImpersonationBanner source={source} />\n\n if (!source?.widgets.search) return banner\n\n return (\n <>\n {banner}\n <span data-slot=\"starter-kit-search\" className=\"inline-flex\">\n <LoadedSearch source={source} />\n </span>\n </>\n )\n}\n\n/**\n * The bell with the user's database notifications (`databaseNotifications()`), polling and\n * realtime updates as in the panel. Renders nothing for guests or without database\n * notifications.\n */\nexport function AlfakitNotifications({ panel }: AlfakitHeaderWidgetProps = {}) {\n const source = useWidgets(panel, 'notifications')\n\n const banner = <StarterKitImpersonationBanner source={source} />\n\n if (!source?.widgets.notifications) return banner\n\n return (\n <>\n {banner}\n <span data-slot=\"starter-kit-notifications\" className=\"inline-flex\">\n <LoadedNotifications source={source} />\n </span>\n </>\n )\n}\n\n/**\n * The tenant switcher of a tenancy panel: the current tenant inside the panel, or a menu\n * to choose one on the host's own pages. Renders nothing for guests or other panels.\n */\nexport function AlfakitTenantSwitcher({ panel }: AlfakitHeaderWidgetProps = {}) {\n const source = useWidgets(panel, 'tenancy')\n\n const banner = <StarterKitImpersonationBanner source={source} />\n\n if (!source?.widgets.tenancy) return banner\n\n return (\n <>\n {banner}\n <span data-slot=\"starter-kit-tenant-switcher\" className=\"inline-flex\">\n <LoadedTenantSwitcher source={source} />\n </span>\n </>\n )\n}\n\n/**\n * The AI copilot of alfasix/alfakit-ai (`AiPlugin::copilot()`): its button and chat\n * slide-over, opened with its key bindings (`mod+j`) from any page. Renders nothing for\n * guests, without the plugin's copilot, for users who may not use it and (tenancy panels)\n * outside a tenant.\n */\nexport function AlfakitCopilot({ panel }: AlfakitHeaderWidgetProps = {}) {\n const source = useWidgets(panel, 'copilot')\n\n const banner = <StarterKitImpersonationBanner source={source} />\n\n if (!source?.widgets.copilot) return banner\n\n return (\n <>\n {banner}\n <span data-slot=\"starter-kit-copilot\" className=\"inline-flex\">\n <LoadedCopilot source={source} />\n </span>\n </>\n )\n}\n"],"mappings":"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"}
|
package/dist/styles.css
CHANGED
|
@@ -11,8 +11,21 @@
|
|
|
11
11
|
/* Built-in themes (alfakit, minimal, soft, corporate, filament); neutral is below. */
|
|
12
12
|
@import './themes.css';
|
|
13
13
|
|
|
14
|
+
/*
|
|
15
|
+
* Toast styles, as in the Vue renderer. Sonner also injects them at runtime, which a
|
|
16
|
+
* nonce-based Content Security Policy blocks; from the stylesheet they always apply.
|
|
17
|
+
*/
|
|
18
|
+
@import 'sonner/dist/styles.css';
|
|
19
|
+
|
|
14
20
|
@custom-variant dark (&:is(.dark *));
|
|
15
21
|
|
|
22
|
+
/*
|
|
23
|
+
* Class lists that live in @alfakit/core (stacked-on-mobile tables, filter and column manager
|
|
24
|
+
* widths in `table.ts`, …): scan core's build too. Relative to this file in
|
|
25
|
+
* node_modules/@alfakit/<renderer>/dist, next to node_modules/@alfakit/core.
|
|
26
|
+
*/
|
|
27
|
+
@source '../../core/dist';
|
|
28
|
+
|
|
16
29
|
/*
|
|
17
30
|
* Grid classes are composed at runtime from schema breakpoints (`columns`, `columnSpan`),
|
|
18
31
|
* so force Tailwind to generate every variant the renderers can produce.
|
|
@@ -116,10 +129,10 @@
|
|
|
116
129
|
--secondary: oklch(0.97 0 0);
|
|
117
130
|
--secondary-foreground: oklch(0.205 0 0);
|
|
118
131
|
--muted: oklch(0.97 0 0);
|
|
119
|
-
--muted-foreground: oklch(0.
|
|
132
|
+
--muted-foreground: oklch(0.54 0 0);
|
|
120
133
|
--accent: oklch(0.97 0 0);
|
|
121
134
|
--accent-foreground: oklch(0.205 0 0);
|
|
122
|
-
--destructive: var(--alfakit-danger, oklch(0.
|
|
135
|
+
--destructive: var(--alfakit-danger, oklch(0.5 0.245 27.325));
|
|
123
136
|
--destructive-foreground: var(--alfakit-danger-foreground, oklch(0.985 0 0));
|
|
124
137
|
--border: oklch(0.922 0 0);
|
|
125
138
|
--input: oklch(0.64 0 0);
|
package/dist/themes.css
CHANGED
|
@@ -287,7 +287,7 @@
|
|
|
287
287
|
--secondary: oklch(0.967 0.003 264.542);
|
|
288
288
|
--secondary-foreground: oklch(0.21 0.034 264.665);
|
|
289
289
|
--muted: oklch(0.967 0.003 264.542);
|
|
290
|
-
--muted-foreground: oklch(0.
|
|
290
|
+
--muted-foreground: oklch(0.535 0.027 264.364);
|
|
291
291
|
--accent: oklch(0.967 0.003 264.542);
|
|
292
292
|
--accent-foreground: oklch(0.21 0.034 264.665);
|
|
293
293
|
--border: oklch(0.928 0.006 264.531);
|
|
@@ -678,6 +678,11 @@
|
|
|
678
678
|
color: var(--primary);
|
|
679
679
|
}
|
|
680
680
|
|
|
681
|
+
/* The schema inspector's round button (dev tools) sits above the bar, not on its last item. */
|
|
682
|
+
:root[data-mobile='bottom'] [data-slot='dev-tools-toggle'] {
|
|
683
|
+
bottom: calc(var(--alfakit-bottom-nav-height) + env(safe-area-inset-bottom, 0px) + 1rem);
|
|
684
|
+
}
|
|
685
|
+
|
|
681
686
|
/* Room for the bar, sticky actions sit right above it. */
|
|
682
687
|
:root[data-mobile='bottom'] [data-slot='content'] {
|
|
683
688
|
padding-bottom: calc(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfakit/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "React + shadcn/ui renderer for Alfakit, the Laravel admin panel builder by Alfa Six.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alfa Six (https://alfasixbh.com)",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"react-day-picker": "^10.0.2",
|
|
97
97
|
"sonner": "^2.0.8",
|
|
98
98
|
"tailwind-merge": "^3.7.0",
|
|
99
|
-
"@alfakit/core": "0.1.
|
|
99
|
+
"@alfakit/core": "0.1.6"
|
|
100
100
|
},
|
|
101
101
|
"peerDependencies": {
|
|
102
102
|
"@inertiajs/react": "^3.0.0",
|
|
@@ -222,6 +222,16 @@ function ActionModal({ modal, hidden = false }: { modal: OpenModal; hidden?: boo
|
|
|
222
222
|
|
|
223
223
|
const submit = (createAnother = false) => {
|
|
224
224
|
submittedRef.current = true
|
|
225
|
+
|
|
226
|
+
// A click that blurred a live field started a refresh: send the state it returns.
|
|
227
|
+
if (form) {
|
|
228
|
+
form.whenSettled(() => run(createAnother))
|
|
229
|
+
} else {
|
|
230
|
+
run(createAnother)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const run = (createAnother: boolean) => {
|
|
225
235
|
const values = form?.state.values ?? {}
|
|
226
236
|
const options = {
|
|
227
237
|
...(schema.passwordConfirmation ? { password } : {}),
|
|
@@ -29,7 +29,14 @@ export function useCopilotState(controller: CopilotController): CopilotState {
|
|
|
29
29
|
* a chat slide-over. Questions, tools and actions run on the server; proposed actions
|
|
30
30
|
* run only when the user confirms them in the chat.
|
|
31
31
|
*/
|
|
32
|
-
export function AiCopilot({
|
|
32
|
+
export function AiCopilot({
|
|
33
|
+
component,
|
|
34
|
+
autoOpen = false,
|
|
35
|
+
}: {
|
|
36
|
+
component: AiCopilotSchema
|
|
37
|
+
/** Open once mounted (starter kit header: clicked or `mod+j` before its code loaded). */
|
|
38
|
+
autoOpen?: boolean
|
|
39
|
+
}) {
|
|
33
40
|
const t = useT()
|
|
34
41
|
// One controller per panel: Inertia visits pass new props objects.
|
|
35
42
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -42,6 +49,12 @@ export function AiCopilot({ component }: { component: AiCopilotSchema }) {
|
|
|
42
49
|
// The panel mounts the first time the copilot opens (button or shortcut), then stays.
|
|
43
50
|
useEffect(() => controller.subscribe((next) => next.open && setMounted(true)), [controller])
|
|
44
51
|
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (autoOpen && component.canUse !== false) controller.open()
|
|
54
|
+
// Only when it mounts.
|
|
55
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
56
|
+
}, [controller])
|
|
57
|
+
|
|
45
58
|
useEffect(() => {
|
|
46
59
|
const bindings = JSON.parse(keys) as string[]
|
|
47
60
|
|
|
@@ -78,6 +78,9 @@ export function CmsLivePreview({ component }: { component: CmsLivePreviewSchema
|
|
|
78
78
|
const autosaveRef = useRef<CmsAutosave | null>(null)
|
|
79
79
|
const status =
|
|
80
80
|
snapshot.hasChanges && component.changedStatus ? component.changedStatus : component.status
|
|
81
|
+
// "Publish changes" appears as soon as an autosave makes the draft differ.
|
|
82
|
+
const actions =
|
|
83
|
+
snapshot.hasChanges && component.changedActions ? component.changedActions : component.actions
|
|
81
84
|
|
|
82
85
|
// Measured in the browser after mounting: stick right under the panel's topbar when it
|
|
83
86
|
// sticks too, and open the pane beside the form on wide screens (the viewer's last
|
|
@@ -220,9 +223,9 @@ export function CmsLivePreview({ component }: { component: CmsLivePreviewSchema
|
|
|
220
223
|
: t('cms.live_preview', {}, 'Live preview')}
|
|
221
224
|
</Button>
|
|
222
225
|
)}
|
|
223
|
-
{
|
|
226
|
+
{actions && actions.length > 0 && (
|
|
224
227
|
<div onPointerDownCapture={() => void autosaveRef.current?.flush()}>
|
|
225
|
-
<ActionList actions={
|
|
228
|
+
<ActionList actions={actions} context={actionContext} size="sm" />
|
|
226
229
|
</div>
|
|
227
230
|
)}
|
|
228
231
|
</div>
|
|
@@ -89,7 +89,7 @@ function BuilderFieldContent({ component }: { component: BuilderSchema }) {
|
|
|
89
89
|
const context = useFieldActionContext(component)
|
|
90
90
|
const runner = useActionRunner()
|
|
91
91
|
const runItemAction = useRunItemAction(context)
|
|
92
|
-
const fallbacks = useItemActionFallbacks(component.addActionLabel)
|
|
92
|
+
const fallbacks = useItemActionFallbacks(component.addActionLabel, 'builder')
|
|
93
93
|
const resolve = useItemActions(component, fallbacks)
|
|
94
94
|
// Long lists render only the visible blocks (plus a few around them).
|
|
95
95
|
const virtual = useVirtualList(
|
|
@@ -142,6 +142,13 @@ function BuilderFieldContent({ component }: { component: BuilderSchema }) {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
const blockOf = (type: string) => component.blocks.find((block) => block.name === type)
|
|
145
|
+
// Summaries are made on the server: collapsing items whose block has one brings them up
|
|
146
|
+
// to date with the edits.
|
|
147
|
+
const refreshSummaries = (indexes: number[]) => {
|
|
148
|
+
if (indexes.some((index) => blockOf(items[index]?.type ?? '')?.summary)) {
|
|
149
|
+
void form?.refresh(component.statePath)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
145
152
|
const underMax = component.maxItems == null || items.length < component.maxItems
|
|
146
153
|
const canAdd = component.addable && !locked && underMax
|
|
147
154
|
const canDelete =
|
|
@@ -197,9 +204,10 @@ function BuilderFieldContent({ component }: { component: BuilderSchema }) {
|
|
|
197
204
|
<CollapseAllActions
|
|
198
205
|
context={context}
|
|
199
206
|
resolve={(name) => resolve(name)}
|
|
200
|
-
onCollapseAll={() =>
|
|
207
|
+
onCollapseAll={() => {
|
|
201
208
|
setCollapsed(Object.fromEntries(items.map((_, index) => [index, true])))
|
|
202
|
-
|
|
209
|
+
refreshSummaries(items.map((_, index) => index))
|
|
210
|
+
}}
|
|
203
211
|
onExpandAll={() =>
|
|
204
212
|
setCollapsed(Object.fromEntries(items.map((_, index) => [index, false])))
|
|
205
213
|
}
|
|
@@ -317,9 +325,10 @@ function BuilderFieldContent({ component }: { component: BuilderSchema }) {
|
|
|
317
325
|
onMove={moveWithButton}
|
|
318
326
|
onClone={() => cloneItem(index)}
|
|
319
327
|
onDelete={() => removeItem(index)}
|
|
320
|
-
onToggle={() =>
|
|
328
|
+
onToggle={() => {
|
|
321
329
|
setCollapsed((state) => ({ ...state, [index]: !isCollapsed }))
|
|
322
|
-
|
|
330
|
+
if (!isCollapsed) refreshSummaries([index])
|
|
331
|
+
}}
|
|
323
332
|
>
|
|
324
333
|
{hasPreview && !locked && (
|
|
325
334
|
<Button
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
ImageEditorData,
|
|
5
5
|
ImageEditorDragMode,
|
|
6
6
|
} from '@alfakit/core'
|
|
7
|
-
import { ImageEditor, loadEditorImage } from '@alfakit/core'
|
|
7
|
+
import { ImageEditor, imageEditorOptionsKey, loadEditorImage } from '@alfakit/core'
|
|
8
8
|
import {
|
|
9
9
|
FlipHorizontal2Icon,
|
|
10
10
|
FlipVertical2Icon,
|
|
@@ -63,9 +63,14 @@ export function ImageEditorDialog({
|
|
|
63
63
|
const [saving, setSaving] = useState(false)
|
|
64
64
|
const hintId = useId()
|
|
65
65
|
|
|
66
|
+
// Restart only for another image or other settings: the schema object itself is new on
|
|
67
|
+
// every render of a template item (a new builder block), which kept restarting the load.
|
|
68
|
+
const optionsKey = imageEditorOptionsKey(options)
|
|
69
|
+
|
|
66
70
|
useEffect(() => {
|
|
67
71
|
if (!request) return
|
|
68
72
|
|
|
73
|
+
const options = JSON.parse(optionsKey) as FileUploadImageEditorSchema
|
|
69
74
|
let cancelled = false
|
|
70
75
|
|
|
71
76
|
loadEditorImage(request.file)
|
|
@@ -97,7 +102,7 @@ export function ImageEditorDialog({
|
|
|
97
102
|
return null
|
|
98
103
|
})
|
|
99
104
|
}
|
|
100
|
-
}, [request,
|
|
105
|
+
}, [request, optionsKey])
|
|
101
106
|
|
|
102
107
|
const finish = (file: File | null) => {
|
|
103
108
|
request?.resolve(file)
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
ItemMoveTracker,
|
|
13
13
|
addActionJustify,
|
|
14
14
|
itemActionContext,
|
|
15
|
+
itemActionIcon,
|
|
15
16
|
resolveItemAction,
|
|
16
17
|
withItemActionState,
|
|
17
18
|
} from '@alfakit/core'
|
|
@@ -154,19 +155,41 @@ export function useRunItemAction(context: ActionContext) {
|
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
/** Default looks of the built-in actions, for payloads without `itemActions
|
|
158
|
-
export function useItemActionFallbacks(
|
|
158
|
+
/** Default looks of the built-in actions, for payloads without `itemActions` (icons follow their `alfakit::` aliases). */
|
|
159
|
+
export function useItemActionFallbacks(
|
|
160
|
+
addLabel = '',
|
|
161
|
+
kind: 'repeater' | 'builder' = 'repeater',
|
|
162
|
+
): Record<ItemActionName, ItemActionFallback> {
|
|
159
163
|
const t = useT()
|
|
160
164
|
|
|
161
165
|
return {
|
|
162
166
|
add: { label: addLabel, icon: 'plus', variant: 'outline' },
|
|
163
|
-
delete: {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
167
|
+
delete: {
|
|
168
|
+
label: t('form.remove_item', {}, 'Remove'),
|
|
169
|
+
icon: itemActionIcon(kind, 'delete', 'trash-2'),
|
|
170
|
+
color: 'danger',
|
|
171
|
+
},
|
|
172
|
+
clone: { label: t('form.clone', {}, 'Clone'), icon: itemActionIcon(kind, 'clone', 'copy') },
|
|
173
|
+
reorder: {
|
|
174
|
+
label: t('form.reorder_item', {}, 'Reorder'),
|
|
175
|
+
icon: itemActionIcon(kind, 'reorder', 'grip-vertical'),
|
|
176
|
+
},
|
|
177
|
+
moveUp: {
|
|
178
|
+
label: t('form.move_up', {}, 'Move up'),
|
|
179
|
+
icon: itemActionIcon(kind, 'move-up', 'chevron-up'),
|
|
180
|
+
},
|
|
181
|
+
moveDown: {
|
|
182
|
+
label: t('form.move_down', {}, 'Move down'),
|
|
183
|
+
icon: itemActionIcon(kind, 'move-down', 'chevron-down'),
|
|
184
|
+
},
|
|
185
|
+
collapse: {
|
|
186
|
+
label: t('form.collapse_item', {}, 'Collapse'),
|
|
187
|
+
icon: itemActionIcon(kind, 'collapse', 'chevron-up'),
|
|
188
|
+
},
|
|
189
|
+
expand: {
|
|
190
|
+
label: t('form.expand_item', {}, 'Expand'),
|
|
191
|
+
icon: itemActionIcon(kind, 'expand', 'chevron-down'),
|
|
192
|
+
},
|
|
170
193
|
collapseAll: {
|
|
171
194
|
label: t('form.collapse_all', {}, 'Collapse all'),
|
|
172
195
|
icon: 'chevrons-down-up',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
fieldIds,
|
|
3
|
+
itemActionIcon,
|
|
3
4
|
moveRepeaterItem,
|
|
4
5
|
reorderedItemKeys,
|
|
5
6
|
resolveItemAction,
|
|
@@ -51,11 +52,11 @@ export function KeyValueField({ component }: { component: KeyValueSchema }) {
|
|
|
51
52
|
})
|
|
52
53
|
const deleteAction = resolveItemAction('delete', component, undefined, {
|
|
53
54
|
label: t('form.remove_row', {}, 'Remove row'),
|
|
54
|
-
icon: 'trash-2',
|
|
55
|
+
icon: itemActionIcon('key-value', 'delete', 'trash-2'),
|
|
55
56
|
})
|
|
56
57
|
const reorderAction = resolveItemAction('reorder', component, undefined, {
|
|
57
58
|
label: t('form.reorder_item', {}, 'Reorder'),
|
|
58
|
-
icon: 'grip-vertical',
|
|
59
|
+
icon: itemActionIcon('key-value', 'reorder', 'grip-vertical'),
|
|
59
60
|
})
|
|
60
61
|
|
|
61
62
|
const update = (next: Row[]) => setValue(next)
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
XIcon,
|
|
21
21
|
} from 'lucide-react'
|
|
22
22
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
23
|
-
import { useChoice, useT } from '../../context'
|
|
23
|
+
import { useChoice, useFormatter, useT } from '../../context'
|
|
24
24
|
import { useStore } from '../../hooks/use-store'
|
|
25
25
|
import { Icon } from '../../lib/icons'
|
|
26
26
|
import { cn } from '../../lib/utils'
|
|
@@ -135,6 +135,7 @@ interface PickerProps {
|
|
|
135
135
|
|
|
136
136
|
function ModalTablePicker({ component, initial, onClose, onConfirm }: PickerProps) {
|
|
137
137
|
const t = useT()
|
|
138
|
+
const format = useFormatter()
|
|
138
139
|
const choice = useChoice()
|
|
139
140
|
const form = useFormStore()
|
|
140
141
|
const multiple = Boolean(component.multiple)
|
|
@@ -399,8 +400,12 @@ function ModalTablePicker({ component, initial, onClose, onConfirm }: PickerProp
|
|
|
399
400
|
{paging.total !== undefined
|
|
400
401
|
? t(
|
|
401
402
|
'table.showing',
|
|
402
|
-
{
|
|
403
|
-
|
|
403
|
+
{
|
|
404
|
+
first: format.number(paging.from),
|
|
405
|
+
last: format.number(paging.to),
|
|
406
|
+
total: format.number(paging.total),
|
|
407
|
+
},
|
|
408
|
+
'Showing :first–:last of :total',
|
|
404
409
|
)
|
|
405
410
|
: `${paging.from}–${paging.to}`}
|
|
406
411
|
</span>
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
removeIndexedState,
|
|
14
14
|
removeRepeaterItem,
|
|
15
15
|
reorderedItemKeys,
|
|
16
|
-
|
|
16
|
+
templateForIndex,
|
|
17
17
|
virtualizeOptions,
|
|
18
18
|
} from '@alfakit/core'
|
|
19
19
|
import { useState } from 'react'
|
|
@@ -125,8 +125,7 @@ function RepeaterFieldContent({ component }: { component: RepeaterSchema }) {
|
|
|
125
125
|
)
|
|
126
126
|
|
|
127
127
|
const schemaFor = (index: number): SchemaComponent[] =>
|
|
128
|
-
component.items[index]?.schema ??
|
|
129
|
-
replacePlaceholder(component.template, '__INDEX__', String(index))
|
|
128
|
+
component.items[index]?.schema ?? templateForIndex(component.template, index)
|
|
130
129
|
|
|
131
130
|
const canAdd =
|
|
132
131
|
component.addable &&
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
attachToolbarKeyboard,
|
|
10
10
|
commandLabel,
|
|
11
11
|
detailsNodeConfigs,
|
|
12
|
+
documentNonce,
|
|
12
13
|
EditorAttachments,
|
|
13
14
|
fieldIds,
|
|
14
15
|
fileAttachmentError,
|
|
@@ -403,6 +404,8 @@ function RichEditorInput({
|
|
|
403
404
|
useEffect(() => () => attachments.destroy(), [attachments])
|
|
404
405
|
|
|
405
406
|
const editor = useEditor({
|
|
407
|
+
// TipTap injects its base CSS; with the page's CSP nonce a nonce-based policy allows it.
|
|
408
|
+
injectNonce: documentNonce(),
|
|
406
409
|
extensions: [
|
|
407
410
|
StarterKit.configure({ link: false }),
|
|
408
411
|
Link.configure(linkExtensionOptions(component.linkProtocols)),
|
|
@@ -128,7 +128,8 @@ export function FormBlock({ block }: { block: FormBlockSchema }) {
|
|
|
128
128
|
submitting.current = true
|
|
129
129
|
submittedBlock.current = latestBlock.current
|
|
130
130
|
store.setProcessing(true)
|
|
131
|
-
|
|
131
|
+
// A click that blurred a live field started a refresh: send the state it returns.
|
|
132
|
+
store.whenSettled(() => runner.execute(action, ctx, store.state.values))
|
|
132
133
|
},
|
|
133
134
|
[runner, store],
|
|
134
135
|
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BaseField, FieldAriaAttributes, FormState, FormStore } from '@alfakit/core'
|
|
2
|
-
import { fieldAria, fieldDomId } from '@alfakit/core'
|
|
2
|
+
import { fieldAria, fieldDomId, fieldError } from '@alfakit/core'
|
|
3
3
|
import { createContext, useCallback, useContext, useMemo, type ReactNode } from 'react'
|
|
4
4
|
import { useStore, useStoreSelector } from '../../hooks/use-store'
|
|
5
5
|
|
|
@@ -60,7 +60,7 @@ export function useField<T = unknown>(field: BaseField): FieldBinding<T> {
|
|
|
60
60
|
return current as T
|
|
61
61
|
})
|
|
62
62
|
|
|
63
|
-
const error = useStoreSelector(store.store, (state) => state.errors
|
|
63
|
+
const error = useStoreSelector(store.store, (state) => fieldError(state.errors, path))
|
|
64
64
|
|
|
65
65
|
const setValue = useCallback((next: T) => store.setValue(path, next), [store, path])
|
|
66
66
|
const blur = useCallback(() => store.blur(path), [store, path])
|
|
@@ -28,7 +28,14 @@ import {
|
|
|
28
28
|
SearchIcon,
|
|
29
29
|
} from 'lucide-react'
|
|
30
30
|
import { Fragment, useEffect, useId, useRef, useState, type KeyboardEvent } from 'react'
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
useActionRunner,
|
|
33
|
+
useChoice,
|
|
34
|
+
useDirection,
|
|
35
|
+
useFormatter,
|
|
36
|
+
usePanel,
|
|
37
|
+
useT,
|
|
38
|
+
} from '../../context'
|
|
32
39
|
import { useStore } from '../../hooks/use-store'
|
|
33
40
|
import { badgeColorClasses, colorClass, cssColor } from '../../lib/colors'
|
|
34
41
|
import { Icon } from '../../lib/icons'
|
|
@@ -424,6 +431,7 @@ function ColumnHeader({
|
|
|
424
431
|
onCreate: () => void
|
|
425
432
|
t: Translate
|
|
426
433
|
}) {
|
|
434
|
+
const choice = useChoice()
|
|
427
435
|
const wip = kanbanWipState(column)
|
|
428
436
|
const shown =
|
|
429
437
|
column.wipLimit !== undefined ? column.count : filtered ? column.total : column.count
|
|
@@ -436,7 +444,7 @@ function ColumnHeader({
|
|
|
436
444
|
{ shown: column.total, count: column.count },
|
|
437
445
|
':shown of :count cards',
|
|
438
446
|
)
|
|
439
|
-
:
|
|
447
|
+
: choice('kanban.count', column.count, {}, '{1} :count card|[0,*] :count cards')
|
|
440
448
|
const style = column.color ? { borderTopColor: cssColor(column.color) } : undefined
|
|
441
449
|
const toggleLabel = collapsed
|
|
442
450
|
? t('kanban.expand', { column: column.label }, 'Expand :column')
|
|
@@ -532,7 +540,12 @@ function ColumnHeader({
|
|
|
532
540
|
)}
|
|
533
541
|
{wip === 'over' && (
|
|
534
542
|
<p className="text-destructive text-xs">
|
|
535
|
-
{
|
|
543
|
+
{choice(
|
|
544
|
+
'kanban.wip_over',
|
|
545
|
+
column.wipLimit ?? 0,
|
|
546
|
+
{ limit: column.wipLimit ?? 0 },
|
|
547
|
+
'{1} Over the limit of :limit card|[0,*] Over the limit of :limit cards',
|
|
548
|
+
)}
|
|
536
549
|
</p>
|
|
537
550
|
)}
|
|
538
551
|
{wip === 'full' && column.wipStrict && (
|
|
@@ -76,11 +76,11 @@ export function SectionComponent({ component }: { component: SectionSchema }) {
|
|
|
76
76
|
{component.heading &&
|
|
77
77
|
createElement(
|
|
78
78
|
headingTag(component.headingLevel),
|
|
79
|
-
{ className: 'text-base leading-6 font-semibold' },
|
|
79
|
+
{ className: 'text-base leading-6 font-semibold break-words' },
|
|
80
80
|
component.heading,
|
|
81
81
|
)}
|
|
82
82
|
{component.description && (
|
|
83
|
-
<p className="text-muted-foreground mt-1 text-sm">{component.description}</p>
|
|
83
|
+
<p className="text-muted-foreground mt-1 text-sm break-words">{component.description}</p>
|
|
84
84
|
)}
|
|
85
85
|
</div>
|
|
86
86
|
{component.headerActions && component.headerActions.length > 0 && (
|