@panneau/app 5.0.0-alpha.4 → 5.0.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +32 -5
  2. package/dist/Panneau.css +19 -0
  3. package/dist/Panneau.d.ts +7 -1
  4. package/dist/Panneau.d.ts.map +1 -1
  5. package/dist/Panneau.js +137 -30
  6. package/dist/Panneau.js.map +1 -1
  7. package/dist/Panneau.module.js +12 -0
  8. package/dist/Panneau.module.js.map +1 -0
  9. package/dist/Shell.d.ts +8 -3
  10. package/dist/Shell.d.ts.map +1 -1
  11. package/dist/Shell.js +272 -114
  12. package/dist/Shell.js.map +1 -1
  13. package/dist/createPanneauApp.d.ts +3 -0
  14. package/dist/createPanneauApp.d.ts.map +1 -1
  15. package/dist/createPanneauApp.js.map +1 -1
  16. package/dist/cx.d.ts +3 -0
  17. package/dist/cx.d.ts.map +1 -0
  18. package/dist/cx.js +9 -0
  19. package/dist/cx.js.map +1 -0
  20. package/dist/documentTitle.d.ts +10 -0
  21. package/dist/documentTitle.d.ts.map +1 -0
  22. package/dist/documentTitle.js +49 -0
  23. package/dist/documentTitle.js.map +1 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2 -1
  27. package/dist/location.d.ts +13 -1
  28. package/dist/location.d.ts.map +1 -1
  29. package/dist/location.js +55 -48
  30. package/dist/location.js.map +1 -1
  31. package/dist/navigation.d.ts +53 -0
  32. package/dist/navigation.d.ts.map +1 -0
  33. package/dist/navigation.js +117 -0
  34. package/dist/navigation.js.map +1 -0
  35. package/dist/pages/HomePage.d.ts +11 -0
  36. package/dist/pages/HomePage.d.ts.map +1 -0
  37. package/dist/pages/HomePage.js +111 -0
  38. package/dist/pages/HomePage.js.map +1 -0
  39. package/dist/pages/ItemPanel.css +164 -0
  40. package/dist/pages/ItemPanel.d.ts +17 -0
  41. package/dist/pages/ItemPanel.d.ts.map +1 -0
  42. package/dist/pages/ItemPanel.js +370 -0
  43. package/dist/pages/ItemPanel.js.map +1 -0
  44. package/dist/pages/ItemPanel.module.js +34 -0
  45. package/dist/pages/ItemPanel.module.js.map +1 -0
  46. package/dist/pages/ItemPreview.d.ts +15 -0
  47. package/dist/pages/ItemPreview.d.ts.map +1 -0
  48. package/dist/pages/ItemPreview.js +179 -0
  49. package/dist/pages/ItemPreview.js.map +1 -0
  50. package/dist/pages/NotFoundPage.d.ts +6 -0
  51. package/dist/pages/NotFoundPage.d.ts.map +1 -0
  52. package/dist/pages/NotFoundPage.js +93 -0
  53. package/dist/pages/NotFoundPage.js.map +1 -0
  54. package/dist/pages/PageHeader.d.ts +21 -0
  55. package/dist/pages/PageHeader.d.ts.map +1 -0
  56. package/dist/pages/PageHeader.js +86 -0
  57. package/dist/pages/PageHeader.js.map +1 -0
  58. package/dist/pages/ResourceFormPage.d.ts +15 -0
  59. package/dist/pages/ResourceFormPage.d.ts.map +1 -0
  60. package/dist/pages/ResourceFormPage.js +204 -0
  61. package/dist/pages/ResourceFormPage.js.map +1 -0
  62. package/dist/pages/ResourceListPage.d.ts +15 -0
  63. package/dist/pages/ResourceListPage.d.ts.map +1 -0
  64. package/dist/pages/ResourceListPage.js +101 -0
  65. package/dist/pages/ResourceListPage.js.map +1 -0
  66. package/dist/pages/itemTitle.d.ts +7 -0
  67. package/dist/pages/itemTitle.d.ts.map +1 -0
  68. package/dist/pages/itemTitle.js +18 -0
  69. package/dist/pages/itemTitle.js.map +1 -0
  70. package/dist/pages/pages.css +162 -0
  71. package/dist/pages/pages.module.js +40 -0
  72. package/dist/pages/pages.module.js.map +1 -0
  73. package/dist/preferences.d.ts +6 -0
  74. package/dist/preferences.d.ts.map +1 -0
  75. package/dist/preferences.js +53 -0
  76. package/dist/preferences.js.map +1 -0
  77. package/dist/recentItems.d.ts +15 -0
  78. package/dist/recentItems.d.ts.map +1 -0
  79. package/dist/recentItems.js +70 -0
  80. package/dist/recentItems.js.map +1 -0
  81. package/dist/shell/AppLayout.css +189 -0
  82. package/dist/shell/AppLayout.d.ts +22 -0
  83. package/dist/shell/AppLayout.d.ts.map +1 -0
  84. package/dist/shell/AppLayout.js +365 -0
  85. package/dist/shell/AppLayout.js.map +1 -0
  86. package/dist/shell/AppLayout.module.js +36 -0
  87. package/dist/shell/AppLayout.module.js.map +1 -0
  88. package/dist/shell/GoTo.d.ts +20 -0
  89. package/dist/shell/GoTo.d.ts.map +1 -0
  90. package/dist/shell/GoTo.js +370 -0
  91. package/dist/shell/GoTo.js.map +1 -0
  92. package/dist/shell/Sidebar.css +313 -0
  93. package/dist/shell/Sidebar.d.ts +29 -0
  94. package/dist/shell/Sidebar.d.ts.map +1 -0
  95. package/dist/shell/Sidebar.js +225 -0
  96. package/dist/shell/Sidebar.js.map +1 -0
  97. package/dist/shell/Sidebar.module.js +60 -0
  98. package/dist/shell/Sidebar.module.js.map +1 -0
  99. package/dist/shell/tokens.d.ts +23 -0
  100. package/dist/shell/tokens.d.ts.map +1 -0
  101. package/dist/shell/tokens.js +75 -0
  102. package/dist/shell/tokens.js.map +1 -0
  103. package/package.json +15 -6
package/dist/location.js CHANGED
@@ -1,69 +1,76 @@
1
1
  import { useSyncExternalStore } from "react";
2
- import { c } from "react/compiler-runtime";
3
- import { jsx } from "react/jsx-runtime";
2
+ import "react/compiler-runtime";
3
+ import "react/jsx-runtime";
4
4
  //#region src/location.tsx
5
+ var INDEX_KEY = "panneauIndex";
5
6
  var listeners = /* @__PURE__ */ new Set();
7
+ var guard = null;
8
+ var current = 0;
9
+ var confirmed = false;
10
+ function readIndex(state) {
11
+ const value = state !== null && typeof state === "object" ? state[INDEX_KEY] : void 0;
12
+ return typeof value === "number" ? value : null;
13
+ }
14
+ function notify() {
15
+ listeners.forEach((listener) => listener());
16
+ }
17
+ function onPopState(event) {
18
+ const index = readIndex(event.state);
19
+ if (index !== null && index !== current && !confirmed && guard?.isBlocking() === true) {
20
+ const delta = index - current;
21
+ window.history.go(-delta);
22
+ guard.confirmLeave().then((leave) => {
23
+ if (leave) {
24
+ confirmed = true;
25
+ window.history.go(delta);
26
+ }
27
+ });
28
+ return;
29
+ }
30
+ confirmed = false;
31
+ if (index !== null) current = index;
32
+ notify();
33
+ }
6
34
  function subscribe(listener) {
35
+ if (listeners.size === 0) {
36
+ const index = readIndex(window.history.state);
37
+ if (index === null) window.history.replaceState({
38
+ ...window.history.state,
39
+ [INDEX_KEY]: current
40
+ }, "");
41
+ else current = index;
42
+ window.addEventListener("popstate", onPopState);
43
+ }
7
44
  listeners.add(listener);
8
- window.addEventListener("popstate", listener);
9
45
  return () => {
10
46
  listeners.delete(listener);
11
- window.removeEventListener("popstate", listener);
47
+ if (listeners.size === 0) window.removeEventListener("popstate", onPopState);
12
48
  };
13
49
  }
14
50
  /** The current path, updated on navigation and on back and forward. */
15
51
  function usePathname() {
16
52
  return useSyncExternalStore(subscribe, _temp);
17
53
  }
18
- /** Navigates to a path of the app, without reloading the page. */
54
+ /**
55
+ * Sets what guards going back and forward: the shell's leave guards. Returns a function that
56
+ * removes it.
57
+ */
19
58
  function _temp() {
20
59
  return window.location.pathname;
21
60
  }
22
- function navigate(path) {
23
- window.history.pushState(null, "", path);
24
- listeners.forEach((listener) => listener());
61
+ function setHistoryGuard(next) {
62
+ guard = next;
63
+ return () => {
64
+ if (guard === next) guard = null;
65
+ };
25
66
  }
26
- /** A link to a path of the app. Plain clicks navigate without reloading the page. */
27
- function Link(t0) {
28
- const $ = c(8);
29
- let href;
30
- let props;
31
- let t1;
32
- if ($[0] !== t0) {
33
- const { href: t2, onClick, ...t3 } = t0;
34
- href = t2;
35
- props = t3;
36
- t1 = (event) => {
37
- onClick?.(event);
38
- if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || props.target !== void 0 && props.target !== "_self") return;
39
- event.preventDefault();
40
- navigate(href);
41
- };
42
- $[0] = t0;
43
- $[1] = href;
44
- $[2] = props;
45
- $[3] = t1;
46
- } else {
47
- href = $[1];
48
- props = $[2];
49
- t1 = $[3];
50
- }
51
- const handleClick = t1;
52
- let t2;
53
- if ($[4] !== handleClick || $[5] !== href || $[6] !== props) {
54
- t2 = /* @__PURE__ */ jsx("a", {
55
- ...props,
56
- href,
57
- onClick: handleClick
58
- });
59
- $[4] = handleClick;
60
- $[5] = href;
61
- $[6] = props;
62
- $[7] = t2;
63
- } else t2 = $[7];
64
- return t2;
67
+ /** Navigates to a path of the app, without reloading the page. It doesn't ask the guards. */
68
+ function navigate(path) {
69
+ current += 1;
70
+ window.history.pushState({ [INDEX_KEY]: current }, "", path);
71
+ notify();
65
72
  }
66
73
  //#endregion
67
- export { Link, navigate, usePathname };
74
+ export { navigate, setHistoryGuard, usePathname };
68
75
 
69
76
  //# sourceMappingURL=location.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"location.js","names":["href","handleClick","_c"],"sources":["../src/location.tsx"],"sourcesContent":["import { type ComponentProps, type MouseEvent, useSyncExternalStore } from 'react';\n\n// A minimal history-mode location, for the placeholder shell. The shell of Phase 2 replaces it\n// with the app's router.\n\nconst listeners = new Set<() => void>();\n\nfunction subscribe(listener: () => void) {\n listeners.add(listener);\n window.addEventListener('popstate', listener);\n return () => {\n listeners.delete(listener);\n window.removeEventListener('popstate', listener);\n };\n}\n\n/** The current path, updated on navigation and on back and forward. */\nexport function usePathname(): string {\n return useSyncExternalStore(subscribe, () => window.location.pathname);\n}\n\n/** Navigates to a path of the app, without reloading the page. */\nexport function navigate(path: string) {\n window.history.pushState(null, '', path);\n listeners.forEach((listener) => listener());\n}\n\n/** A link to a path of the app. Plain clicks navigate without reloading the page. */\nexport function Link({ href, onClick, ...props }: ComponentProps<'a'> & { href: string }) {\n const handleClick = (event: MouseEvent<HTMLAnchorElement>) => {\n onClick?.(event);\n if (\n event.defaultPrevented ||\n event.button !== 0 ||\n event.metaKey ||\n event.ctrlKey ||\n event.shiftKey ||\n event.altKey ||\n (props.target !== undefined && props.target !== '_self')\n ) {\n return;\n }\n event.preventDefault();\n navigate(href);\n };\n return <a {...props} href={href} onClick={handleClick} />;\n}\n"],"mappings":";;;;AAKA,IAAM,4BAAY,IAAI,IAAgB;AAEtC,SAAS,UAAU,UAAsB;CACrC,UAAU,IAAI,QAAQ;CACtB,OAAO,iBAAiB,YAAY,QAAQ;CAC5C,aAAa;EACT,UAAU,OAAO,QAAQ;EACzB,OAAO,oBAAoB,YAAY,QAAQ;CACnD;AACJ;;AAGA,SAAO,cAAA;CAAA,OACI,qBAAqB,WAAW,KAA8B;AAAC;;AADnE,SAAA,QAAA;CAAA,OAC0C,OAAM,SAAS;AAAS;AAIzE,SAAgB,SAAS,MAAc;CACnC,OAAO,QAAQ,UAAU,MAAM,IAAI,IAAI;CACvC,UAAU,SAAS,aAAa,SAAS,CAAC;AAC9C;;AAGA,SAAO,KAAA,IAAA;CAAA,MAAA,IAAAE,EAAA,CAAA;CAAA,IAAA;CAAA,IAAA;CAAA,IAAA;CAAA,IAAA,EAAA,OAAA,IAAA;EAAc,MAAA,EAAA,MAAA,IAAA,SAAA,GAAA,OAAA;EAAA,OAAA;EAAA,QAAA;EACG,MAAA,UAAA;GAChB,UAAU,KAAK;GACf,IACI,MAAK,oBACL,MAAK,WAAY,KACjB,MAAK,WACL,MAAK,WACL,MAAK,YACL,MAAK,UACJ,MAAK,WAAY,KAAA,KAAa,MAAK,WAAY,SAAQ;GAI5D,MAAK,eAAgB;GACrB,SAAS,IAAI;EAAC;EACjB,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA;EAAA,OAAA,EAAA;EAAA,QAAA,EAAA;EAAA,KAAA,EAAA;CAAA;CAfD,MAAA,cAAoB;CAelB,IAAA;CAAA,IAAA,EAAA,OAAA,eAAA,EAAA,OAAA,QAAA,EAAA,OAAA,OAAA;EACK,KAAA,oBAAA,KAAA;GAAkD,GAA3C;GAAa;GAAeD,SAAA;EAAW,CAAA;EAAI,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CAAA,OAAlD;AAAkD"}
1
+ {"version":3,"file":"location.js","names":["href","handleClick"],"sources":["../src/location.tsx"],"sourcesContent":["import { type ComponentProps, type MouseEvent, useSyncExternalStore } from 'react';\n\n// A minimal history-mode location. Each entry of the app keeps its position in the history\n// (`panneauIndex`), so that a guard can undo going back or forward and ask first.\n\nconst INDEX_KEY = 'panneauIndex';\n\nconst listeners = new Set<() => void>();\n\n/** What guards going back or forward, such as forms with unsaved changes. */\nexport interface HistoryGuard {\n /** Whether going back or forward needs a confirmation now. */\n isBlocking(): boolean;\n /** Resolves `true` to leave, `false` to stay. */\n confirmLeave(): Promise<boolean>;\n}\n\nlet guard: HistoryGuard | null = null;\n// The position of the current entry, and whether the next move is already confirmed\nlet current = 0;\nlet confirmed = false;\n\nfunction readIndex(state: unknown): number | null {\n const value =\n state !== null && typeof state === 'object'\n ? (state as Record<string, unknown>)[INDEX_KEY]\n : undefined;\n return typeof value === 'number' ? value : null;\n}\n\nfunction notify() {\n listeners.forEach((listener) => listener());\n}\n\nfunction onPopState(event: PopStateEvent) {\n const index = readIndex(event.state);\n if (index !== null && index !== current && !confirmed && guard?.isBlocking() === true) {\n // Undo the move, then make it again once the user chose to leave\n const delta = index - current;\n window.history.go(-delta);\n void guard.confirmLeave().then((leave) => {\n if (leave) {\n confirmed = true;\n window.history.go(delta);\n }\n });\n return;\n }\n confirmed = false;\n if (index !== null) {\n current = index;\n }\n notify();\n}\n\nfunction subscribe(listener: () => void) {\n if (listeners.size === 0) {\n // The entry the app starts on\n const index = readIndex(window.history.state);\n if (index === null) {\n window.history.replaceState(\n { ...(window.history.state as object | null), [INDEX_KEY]: current },\n '',\n );\n } else {\n current = index;\n }\n window.addEventListener('popstate', onPopState);\n }\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0) {\n window.removeEventListener('popstate', onPopState);\n }\n };\n}\n\n/** The current path, updated on navigation and on back and forward. */\nexport function usePathname(): string {\n return useSyncExternalStore(subscribe, () => window.location.pathname);\n}\n\n/**\n * Sets what guards going back and forward: the shell's leave guards. Returns a function that\n * removes it.\n */\nexport function setHistoryGuard(next: HistoryGuard): () => void {\n guard = next;\n return () => {\n if (guard === next) {\n guard = null;\n }\n };\n}\n\n/** Navigates to a path of the app, without reloading the page. It doesn't ask the guards. */\nexport function navigate(path: string) {\n current += 1;\n window.history.pushState({ [INDEX_KEY]: current }, '', path);\n notify();\n}\n\n/** A link to a path of the app. Plain clicks navigate without reloading the page. */\nexport function Link({ href, onClick, ...props }: ComponentProps<'a'> & { href: string }) {\n const handleClick = (event: MouseEvent<HTMLAnchorElement>) => {\n onClick?.(event);\n if (\n event.defaultPrevented ||\n event.button !== 0 ||\n event.metaKey ||\n event.ctrlKey ||\n event.shiftKey ||\n event.altKey ||\n (props.target !== undefined && props.target !== '_self')\n ) {\n return;\n }\n event.preventDefault();\n navigate(href);\n };\n return <a {...props} href={href} onClick={handleClick} />;\n}\n"],"mappings":";;;;AAKA,IAAM,YAAY;AAElB,IAAM,4BAAY,IAAI,IAAgB;AAUtC,IAAI,QAA6B;AAEjC,IAAI,UAAU;AACd,IAAI,YAAY;AAEhB,SAAS,UAAU,OAA+B;CAC9C,MAAM,QACF,UAAU,QAAQ,OAAO,UAAU,WAC5B,MAAkC,aACnC,KAAA;CACV,OAAO,OAAO,UAAU,WAAW,QAAQ;AAC/C;AAEA,SAAS,SAAS;CACd,UAAU,SAAS,aAAa,SAAS,CAAC;AAC9C;AAEA,SAAS,WAAW,OAAsB;CACtC,MAAM,QAAQ,UAAU,MAAM,KAAK;CACnC,IAAI,UAAU,QAAQ,UAAU,WAAW,CAAC,aAAa,OAAO,WAAW,MAAM,MAAM;EAEnF,MAAM,QAAQ,QAAQ;EACtB,OAAO,QAAQ,GAAG,CAAC,KAAK;EACxB,MAAW,aAAa,CAAC,CAAC,MAAM,UAAU;GACtC,IAAI,OAAO;IACP,YAAY;IACZ,OAAO,QAAQ,GAAG,KAAK;GAC3B;EACJ,CAAC;EACD;CACJ;CACA,YAAY;CACZ,IAAI,UAAU,MACV,UAAU;CAEd,OAAO;AACX;AAEA,SAAS,UAAU,UAAsB;CACrC,IAAI,UAAU,SAAS,GAAG;EAEtB,MAAM,QAAQ,UAAU,OAAO,QAAQ,KAAK;EAC5C,IAAI,UAAU,MACV,OAAO,QAAQ,aACX;GAAE,GAAI,OAAO,QAAQ;IAA0B,YAAY;EAAQ,GACnE,EACJ;OAEA,UAAU;EAEd,OAAO,iBAAiB,YAAY,UAAU;CAClD;CACA,UAAU,IAAI,QAAQ;CACtB,aAAa;EACT,UAAU,OAAO,QAAQ;EACzB,IAAI,UAAU,SAAS,GACnB,OAAO,oBAAoB,YAAY,UAAU;CAEzD;AACJ;;AAGA,SAAO,cAAA;CAAA,OACI,qBAAqB,WAAW,KAA8B;AAAC;;;;;AADnE,SAAA,QAAA;CAAA,OAC0C,OAAM,SAAS;AAAS;AAOzE,SAAgB,gBAAgB,MAAgC;CAC5D,QAAQ;CACR,aAAa;EACT,IAAI,UAAU,MACV,QAAQ;CAEhB;AACJ;;AAGA,SAAgB,SAAS,MAAc;CACnC,WAAW;CACX,OAAO,QAAQ,UAAU,GAAG,YAAY,QAAQ,GAAG,IAAI,IAAI;CAC3D,OAAO;AACX"}
@@ -0,0 +1,53 @@
1
+ import type { Definition } from '@panneau/protocol';
2
+ import { type AppRoute } from './routes';
3
+ /** What a link of the navigation leads to, for its default icon and to know when it is current. */
4
+ export type NavTarget = {
5
+ kind: 'home';
6
+ } | {
7
+ kind: 'resource';
8
+ resource: string;
9
+ } | {
10
+ kind: 'page';
11
+ page: string;
12
+ } | {
13
+ kind: 'url';
14
+ external: boolean;
15
+ };
16
+ /** A link of the navigation. */
17
+ export interface NavLink {
18
+ type: 'link';
19
+ key: string;
20
+ label: string;
21
+ href: string;
22
+ /** A Lucide icon name from the definition (`"icon": "file-text"`). */
23
+ icon: string | undefined;
24
+ target: NavTarget;
25
+ }
26
+ /** A titled group of links. */
27
+ export interface NavGroup {
28
+ type: 'group';
29
+ key: string;
30
+ label: string | undefined;
31
+ children: NavLink[];
32
+ }
33
+ export interface NavSeparator {
34
+ type: 'separator';
35
+ key: string;
36
+ }
37
+ export type NavEntry = NavLink | NavGroup | NavSeparator;
38
+ /**
39
+ * The path of the user's profile: the user's item in the resource of users (`user.resource`, or
40
+ * the resource `users`), or `null` when the definition has no such resource.
41
+ */
42
+ export declare function getProfilePath(definition: Definition, basePath: string): string | null;
43
+ /** The first letter in upper case: resource labels are written in lower case (`pages`). */
44
+ export declare function capitalize(label: string, locale?: string): string;
45
+ /**
46
+ * The entries of the main navigation: the `main` menu of the definition, or every resource and
47
+ * page without one. Resources the user can't list (`abilities.viewAny`) and entries of unknown
48
+ * types are left out. Groups keep one level: the links of nested groups join their parent.
49
+ */
50
+ export declare function getNavigation(definition: Definition, basePath: string): NavEntry[];
51
+ /** Whether a link leads to the current route: a resource is current on all of its pages. */
52
+ export declare function isCurrent(target: NavTarget, route: AppRoute): boolean;
53
+ //# sourceMappingURL=navigation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../src/navigation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAY,MAAM,mBAAmB,CAAC;AAE9D,OAAO,EAAE,KAAK,QAAQ,EAAa,MAAM,UAAU,CAAC;AAEpD,mGAAmG;AACnG,MAAM,MAAM,SAAS,GACf;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC;AAEzC,gCAAgC;AAChC,MAAM,WAAW,OAAO;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,MAAM,EAAE,SAAS,CAAC;CACrB;AAED,+BAA+B;AAC/B,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,YAAY,CAAC;AAEzD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUtF;AAED,2FAA2F;AAC3F,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjE;AAgED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,CAgBlF;AAED,4FAA4F;AAC5F,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAWrE"}
@@ -0,0 +1,117 @@
1
+ import { routePath } from "./routes.js";
2
+ import { can } from "@panneau/core";
3
+ //#region src/navigation.ts
4
+ /**
5
+ * The path of the user's profile: the user's item in the resource of users (`user.resource`, or
6
+ * the resource `users`), or `null` when the definition has no such resource.
7
+ */
8
+ function getProfilePath(definition, basePath) {
9
+ const user = definition.user;
10
+ if (user === void 0 || user === null) return null;
11
+ const id = user.resource ?? "users";
12
+ const resource = definition.resources.find((candidate) => candidate.id === id);
13
+ return resource !== void 0 ? routePath({
14
+ name: "resource.edit",
15
+ resource: resource.id,
16
+ id: user.id
17
+ }, basePath) : null;
18
+ }
19
+ /** The first letter in upper case: resource labels are written in lower case (`pages`). */
20
+ function capitalize(label, locale) {
21
+ return label.charAt(0).toLocaleUpperCase(locale) + label.slice(1);
22
+ }
23
+ function toLinks(item, definition, basePath, key) {
24
+ const resourceLink = (id, label, icon) => {
25
+ const resource = definition.resources.find((candidate) => candidate.id === id);
26
+ if (resource === void 0 || !can(resource.abilities, "viewAny")) return [];
27
+ return [{
28
+ type: "link",
29
+ key: `${key}:${id}`,
30
+ label: label ?? capitalize(resource.label.plural, definition.locale),
31
+ href: routePath({
32
+ name: "resource.index",
33
+ resource: id
34
+ }, basePath),
35
+ icon: icon ?? resource.icon,
36
+ target: {
37
+ kind: "resource",
38
+ resource: id
39
+ }
40
+ }];
41
+ };
42
+ switch (item.type) {
43
+ case "resources": return definition.resources.flatMap(({ id }) => resourceLink(id));
44
+ case "resource": return item.resource !== void 0 ? resourceLink(item.resource, item.label, item.icon) : [];
45
+ case "page": {
46
+ const page = item.page !== void 0 ? definition.pages?.[item.page] : void 0;
47
+ if (item.page === void 0 || page === void 0) return [];
48
+ return [{
49
+ type: "link",
50
+ key,
51
+ label: item.label ?? page.label ?? item.page,
52
+ href: routePath({
53
+ name: "page",
54
+ page: item.page
55
+ }, basePath),
56
+ icon: item.icon,
57
+ target: {
58
+ kind: "page",
59
+ page: item.page
60
+ }
61
+ }];
62
+ }
63
+ case "link": return item.url !== void 0 ? [{
64
+ type: "link",
65
+ key,
66
+ label: item.label ?? item.url,
67
+ href: item.url,
68
+ icon: item.icon,
69
+ target: {
70
+ kind: "url",
71
+ external: item.external === true
72
+ }
73
+ }] : [];
74
+ case "group": return (item.children ?? []).flatMap((child, index) => toLinks(child, definition, basePath, `${key}.${String(index)}`));
75
+ default: return [];
76
+ }
77
+ }
78
+ /**
79
+ * The entries of the main navigation: the `main` menu of the definition, or every resource and
80
+ * page without one. Resources the user can't list (`abilities.viewAny`) and entries of unknown
81
+ * types are left out. Groups keep one level: the links of nested groups join their parent.
82
+ */
83
+ function getNavigation(definition, basePath) {
84
+ return (definition.menus?.main ?? [{ type: "resources" }, ...Object.keys(definition.pages ?? {}).map((page) => ({
85
+ type: "page",
86
+ page
87
+ }))]).flatMap((item, index) => {
88
+ const key = String(index);
89
+ if (item.type === "separator") return [{
90
+ type: "separator",
91
+ key
92
+ }];
93
+ if (item.type === "group") {
94
+ const children = toLinks(item, definition, basePath, key);
95
+ return children.length > 0 ? [{
96
+ type: "group",
97
+ key,
98
+ label: item.label,
99
+ children
100
+ }] : [];
101
+ }
102
+ return toLinks(item, definition, basePath, key);
103
+ });
104
+ }
105
+ /** Whether a link leads to the current route: a resource is current on all of its pages. */
106
+ function isCurrent(target, route) {
107
+ switch (target.kind) {
108
+ case "home": return route.name === "home";
109
+ case "resource": return "resource" in route && route.resource === target.resource;
110
+ case "page": return route.name === "page" && route.page === target.page;
111
+ case "url": return false;
112
+ }
113
+ }
114
+ //#endregion
115
+ export { capitalize, getNavigation, getProfilePath, isCurrent };
116
+
117
+ //# sourceMappingURL=navigation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.js","names":[],"sources":["../src/navigation.ts"],"sourcesContent":["import { can } from '@panneau/core';\nimport type { Definition, MenuItem } from '@panneau/protocol';\n\nimport { type AppRoute, routePath } from './routes';\n\n/** What a link of the navigation leads to, for its default icon and to know when it is current. */\nexport type NavTarget =\n | { kind: 'home' }\n | { kind: 'resource'; resource: string }\n | { kind: 'page'; page: string }\n | { kind: 'url'; external: boolean };\n\n/** A link of the navigation. */\nexport interface NavLink {\n type: 'link';\n key: string;\n label: string;\n href: string;\n /** A Lucide icon name from the definition (`\"icon\": \"file-text\"`). */\n icon: string | undefined;\n target: NavTarget;\n}\n\n/** A titled group of links. */\nexport interface NavGroup {\n type: 'group';\n key: string;\n label: string | undefined;\n children: NavLink[];\n}\n\nexport interface NavSeparator {\n type: 'separator';\n key: string;\n}\n\nexport type NavEntry = NavLink | NavGroup | NavSeparator;\n\n/**\n * The path of the user's profile: the user's item in the resource of users (`user.resource`, or\n * the resource `users`), or `null` when the definition has no such resource.\n */\nexport function getProfilePath(definition: Definition, basePath: string): string | null {\n const user = definition.user;\n if (user === undefined || user === null) {\n return null;\n }\n const id = user.resource ?? 'users';\n const resource = definition.resources.find((candidate) => candidate.id === id);\n return resource !== undefined\n ? routePath({ name: 'resource.edit', resource: resource.id, id: user.id }, basePath)\n : null;\n}\n\n/** The first letter in upper case: resource labels are written in lower case (`pages`). */\nexport function capitalize(label: string, locale?: string): string {\n return label.charAt(0).toLocaleUpperCase(locale) + label.slice(1);\n}\n\nfunction toLinks(item: MenuItem, definition: Definition, basePath: string, key: string): NavLink[] {\n const resourceLink = (id: string, label?: string, icon?: string): NavLink[] => {\n const resource = definition.resources.find((candidate) => candidate.id === id);\n if (resource === undefined || !can(resource.abilities, 'viewAny')) {\n return [];\n }\n return [\n {\n type: 'link',\n key: `${key}:${id}`,\n label: label ?? capitalize(resource.label.plural, definition.locale),\n href: routePath({ name: 'resource.index', resource: id }, basePath),\n icon: icon ?? resource.icon,\n target: { kind: 'resource', resource: id },\n },\n ];\n };\n switch (item.type) {\n case 'resources':\n return definition.resources.flatMap(({ id }) => resourceLink(id));\n case 'resource':\n return item.resource !== undefined\n ? resourceLink(item.resource, item.label, item.icon)\n : [];\n case 'page': {\n const page = item.page !== undefined ? definition.pages?.[item.page] : undefined;\n if (item.page === undefined || page === undefined) {\n return [];\n }\n return [\n {\n type: 'link',\n key,\n label: item.label ?? page.label ?? item.page,\n href: routePath({ name: 'page', page: item.page }, basePath),\n icon: item.icon,\n target: { kind: 'page', page: item.page },\n },\n ];\n }\n case 'link':\n return item.url !== undefined\n ? [\n {\n type: 'link',\n key,\n label: item.label ?? item.url,\n href: item.url,\n icon: item.icon,\n target: { kind: 'url', external: item.external === true },\n },\n ]\n : [];\n case 'group':\n return (item.children ?? []).flatMap((child, index) =>\n toLinks(child, definition, basePath, `${key}.${String(index)}`),\n );\n default:\n return [];\n }\n}\n\n/**\n * The entries of the main navigation: the `main` menu of the definition, or every resource and\n * page without one. Resources the user can't list (`abilities.viewAny`) and entries of unknown\n * types are left out. Groups keep one level: the links of nested groups join their parent.\n */\nexport function getNavigation(definition: Definition, basePath: string): NavEntry[] {\n const menu: MenuItem[] = definition.menus?.main ?? [\n { type: 'resources' },\n ...Object.keys(definition.pages ?? {}).map((page) => ({ type: 'page', page })),\n ];\n return menu.flatMap((item, index): NavEntry[] => {\n const key = String(index);\n if (item.type === 'separator') {\n return [{ type: 'separator', key }];\n }\n if (item.type === 'group') {\n const children = toLinks(item, definition, basePath, key);\n return children.length > 0 ? [{ type: 'group', key, label: item.label, children }] : [];\n }\n return toLinks(item, definition, basePath, key);\n });\n}\n\n/** Whether a link leads to the current route: a resource is current on all of its pages. */\nexport function isCurrent(target: NavTarget, route: AppRoute): boolean {\n switch (target.kind) {\n case 'home':\n return route.name === 'home';\n case 'resource':\n return 'resource' in route && route.resource === target.resource;\n case 'page':\n return route.name === 'page' && route.page === target.page;\n case 'url':\n return false;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { Definition } from '@panneau/protocol';
2
+ export interface HomePageProps {
3
+ definition: Definition;
4
+ basePath: string;
5
+ }
6
+ /**
7
+ * The home page, until the dashboard of 5.x: a welcome, and a card for each resource the user can
8
+ * list.
9
+ */
10
+ export declare function HomePage({ definition, basePath }: HomePageProps): import("react").JSX.Element;
11
+ //# sourceMappingURL=HomePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HomePage.d.ts","sourceRoot":"","sources":["../../src/pages/HomePage.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AASpD,MAAM,WAAW,aAAa;IAC1B,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,aAAa,+BA4C/D"}
@@ -0,0 +1,111 @@
1
+ import { useDocumentTitle } from "../documentTitle.js";
2
+ import { routePath } from "../routes.js";
3
+ import { capitalize } from "../navigation.js";
4
+ import pages_module_default from "./pages.module.js";
5
+ import { PageHeader } from "./PageHeader.js";
6
+ import { c } from "react/compiler-runtime";
7
+ import { can } from "@panneau/core";
8
+ import { appMessages } from "@panneau/intl";
9
+ import { Icon, getNamedIcon, usePanneauIntl } from "@panneau/ui";
10
+ import { Link } from "react-aria-components";
11
+ import { jsx, jsxs } from "react/jsx-runtime";
12
+ import { List } from "lucide-react";
13
+ //#region src/pages/HomePage.tsx
14
+ /**
15
+ * The home page, until the dashboard of 5.x: a welcome, and a card for each resource the user can
16
+ * list.
17
+ */
18
+ function HomePage(t0) {
19
+ const $ = c(17);
20
+ const { definition, basePath } = t0;
21
+ const intl = usePanneauIntl();
22
+ const resources = definition.resources.filter(_temp);
23
+ const user = definition.user ?? null;
24
+ let t1;
25
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
26
+ t1 = [];
27
+ $[0] = t1;
28
+ } else t1 = $[0];
29
+ useDocumentTitle(t1);
30
+ const t2 = pages_module_default;
31
+ let t3;
32
+ if ($[1] !== definition.name) {
33
+ t3 = /* @__PURE__ */ jsx(PageHeader, { title: definition.name });
34
+ $[1] = definition.name;
35
+ $[2] = t3;
36
+ } else t3 = $[2];
37
+ let t4;
38
+ if ($[3] !== intl || $[4] !== user) {
39
+ t4 = user !== null ? /* @__PURE__ */ jsx("p", {
40
+ className: pages_module_default.intro,
41
+ children: intl.formatMessage(appMessages.welcome, { name: user.name })
42
+ }) : null;
43
+ $[3] = intl;
44
+ $[4] = user;
45
+ $[5] = t4;
46
+ } else t4 = $[5];
47
+ const t5 = pages_module_default;
48
+ let t6;
49
+ if ($[6] !== basePath || $[7] !== definition.locale) {
50
+ t6 = (resource_0) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(Link, {
51
+ href: routePath({
52
+ name: "resource.index",
53
+ resource: resource_0.id
54
+ }, basePath),
55
+ className: pages_module_default.card,
56
+ children: [
57
+ /* @__PURE__ */ jsx("span", {
58
+ className: pages_module_default.cardIcon,
59
+ "aria-hidden": "true",
60
+ children: /* @__PURE__ */ jsx(Icon, { icon: resource_0.icon !== void 0 ? getNamedIcon(resource_0.icon) : List })
61
+ }),
62
+ /* @__PURE__ */ jsx("span", {
63
+ className: pages_module_default.cardTitle,
64
+ children: capitalize(resource_0.label.plural, definition.locale)
65
+ }),
66
+ resource_0.description !== void 0 ? /* @__PURE__ */ jsx("span", {
67
+ className: pages_module_default.cardText,
68
+ children: resource_0.description
69
+ }) : null
70
+ ]
71
+ }) }, resource_0.id);
72
+ $[6] = basePath;
73
+ $[7] = definition.locale;
74
+ $[8] = t6;
75
+ } else t6 = $[8];
76
+ const t7 = resources.map(t6);
77
+ let t8;
78
+ if ($[9] !== t5.cards || $[10] !== t7) {
79
+ t8 = /* @__PURE__ */ jsx("ul", {
80
+ className: t5.cards,
81
+ children: t7
82
+ });
83
+ $[9] = t5.cards;
84
+ $[10] = t7;
85
+ $[11] = t8;
86
+ } else t8 = $[11];
87
+ let t9;
88
+ if ($[12] !== t2.page || $[13] !== t3 || $[14] !== t4 || $[15] !== t8) {
89
+ t9 = /* @__PURE__ */ jsxs("div", {
90
+ className: t2.page,
91
+ children: [
92
+ t3,
93
+ t4,
94
+ t8
95
+ ]
96
+ });
97
+ $[12] = t2.page;
98
+ $[13] = t3;
99
+ $[14] = t4;
100
+ $[15] = t8;
101
+ $[16] = t9;
102
+ } else t9 = $[16];
103
+ return t9;
104
+ }
105
+ function _temp(resource) {
106
+ return can(resource.abilities, "viewAny");
107
+ }
108
+ //#endregion
109
+ export { HomePage };
110
+
111
+ //# sourceMappingURL=HomePage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HomePage.js","names":["styles","resource","_c","styles"],"sources":["../../src/pages/HomePage.tsx"],"sourcesContent":["import { List } from 'lucide-react';\nimport { Link } from 'react-aria-components';\n\nimport { can } from '@panneau/core';\nimport { appMessages } from '@panneau/intl';\nimport type { Definition } from '@panneau/protocol';\nimport { Icon, getNamedIcon, usePanneauIntl } from '@panneau/ui';\n\nimport { useDocumentTitle } from '../documentTitle';\nimport { capitalize } from '../navigation';\nimport { routePath } from '../routes';\nimport { PageHeader } from './PageHeader';\nimport styles from './pages.module.css';\n\nexport interface HomePageProps {\n definition: Definition;\n basePath: string;\n}\n\n/**\n * The home page, until the dashboard of 5.x: a welcome, and a card for each resource the user can\n * list.\n */\nexport function HomePage({ definition, basePath }: HomePageProps) {\n const intl = usePanneauIntl();\n const resources = definition.resources.filter((resource) => can(resource.abilities, 'viewAny'));\n const user = definition.user ?? null;\n useDocumentTitle([]);\n return (\n <div className={styles.page}>\n <PageHeader title={definition.name} />\n {user !== null ? (\n <p className={styles.intro}>\n {intl.formatMessage(appMessages.welcome, { name: user.name })}\n </p>\n ) : null}\n <ul className={styles.cards}>\n {resources.map((resource) => (\n <li key={resource.id}>\n <Link\n href={routePath(\n { name: 'resource.index', resource: resource.id },\n basePath,\n )}\n className={styles.card}\n >\n <span className={styles.cardIcon} aria-hidden=\"true\">\n <Icon\n icon={\n resource.icon !== undefined\n ? getNamedIcon(resource.icon)\n : List\n }\n />\n </span>\n <span className={styles.cardTitle}>\n {capitalize(resource.label.plural, definition.locale)}\n </span>\n {resource.description !== undefined ? (\n <span className={styles.cardText}>{resource.description}</span>\n ) : null}\n </Link>\n </li>\n ))}\n </ul>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuBA,SAAO,SAAA,IAAA;CAAA,MAAA,IAAAE,EAAA,EAAA;CAAkB,MAAA,EAAA,YAAA,aAAA;CACrB,MAAA,OAAa,eAAe;CAC5B,MAAA,YAAkB,WAAU,UAAU,OAAQ,KAAgD;CAC9F,MAAA,OAAa,WAAU,QAAV;CAAwB,IAAA;CAAA,IAAA,EAAA,OAAA,OAAA,IAAA,2BAAA,GAAA;EACpB,KAAA,CAAA;EAAE,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CAAnB,iBAAiB,EAAE;CAECF,MAAAA,KAAAG;CAAM,IAAA;CAAA,IAAA,EAAA,OAAA,WAAA,MAAA;EAClB,KAAA,oBAAC,YAAD,EAAmB,OAAA,WAAU,KAAK,CAAA;EAAI,EAAA,KAAA,WAAA;EAAA,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CAAA,IAAA;CAAA,IAAA,EAAA,OAAA,QAAA,EAAA,OAAA,MAAA;EACrC,KAAA,SAAS,OACN,oBAAA,KAAA;GAAc,WAAAA,qBAAM;GACf,UAAA,KAAI,cAAe,YAAW,SAAU,EAAA,MAAQ,KAAI,KAAM,CAAC;EAC5D,CAAA,IAHP;EAIO,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CACOH,MAAAA,KAAAG;CAAM,IAAA;CAAA,IAAA,EAAA,OAAA,YAAA,EAAA,OAAA,WAAA,QAAA;EACF,MAAA,eACX,oBAAA,MAAA,EAAA,UACI,qBAAC,MAAD;GACU,MAAA,UACF;IAAA,MAAQ;IAAgB,UAAYF,WAAQ;GAAI,GAChD,QACJ;GACW,WAAAE,qBAAM;GALrB,UAAA;IAOI,oBAAA,QAAA;KAAiB,WAAAA,qBAAM;KAAuB,eAAA;KAC1C,UAAA,oBAAC,MAAD,EAEQ,MAAAF,WAAQ,SAAU,KAAA,IACZ,aAAaA,WAAQ,IAClB,IAFT,KAEU,CAAA;IAGf,CAAA;IACP,oBAAA,QAAA;KAAiB,WAAAE,qBAAM;KAClB,UAAA,WAAWF,WAAQ,MAAM,QAAS,WAAU,MAAO;IACjD,CAAA;IACNA,WAAQ,gBAAiB,KAAA,IACtB,oBAAA,QAAA;KAAiB,WAAAE,qBAAM;KAAYF,UAAAA,WAAQ;IAAoB,CAAA,IADlE;GAnBA;EAuBJ,CAAA,EAAA,GAxBIA,WAAQ,EAwBZ;EACR,EAAA,KAAA;EAAA,EAAA,KAAA,WAAA;EAAA,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CA1BA,MAAA,KAAA,UAAS,IAAK,EA0Bd;CAAC,IAAA;CAAA,IAAA,EAAA,OAAA,GAAA,SAAA,EAAA,QAAA,IAAA;EA3BN,KAAA,oBAAA,MAAA;GAAe,WAAAD,GAAM;GAChB,UAAA;EA2BA,CAAA;EAAA,EAAA,KAAA,GAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;CAAA,OAAA,KAAA,EAAA;CAAA,IAAA;CAAA,IAAA,EAAA,QAAA,GAAA,QAAA,EAAA,QAAA,MAAA,EAAA,QAAA,MAAA,EAAA,QAAA,IAAA;EAnCT,KAAA,qBAAA,OAAA;GAAgB,WAAAA,GAAM;GAAtB,UAAA;IACI;IACC;IAKD;GA6BE;;EAAA,EAAA,MAAA,GAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;EAAA,EAAA,MAAA;CAAA,OAAA,KAAA,EAAA;CAAA,OApCN;AAoCM;AA1CP,SAAA,MAAA,UAAA;CAAA,OAEyD,IAAI,SAAQ,WAAY,SAAS;AAAC"}
@@ -0,0 +1,164 @@
1
+ /* The details of an item next to its list. Beside the list on a wide screen, over it otherwise. */
2
+
3
+ .panneau-item-panel-panel {
4
+ position: fixed;
5
+ inset-block: 0;
6
+ inset-inline-end: 0;
7
+ z-index: var(--panneau-z-overlay);
8
+ display: flex;
9
+ flex-direction: column;
10
+ width: min(100%, var(--panneau-item-panel-width, 24rem));
11
+ background: var(--panneau-item-panel-bg, var(--panneau-color-surface));
12
+ border-inline-start: var(--panneau-border-width) solid var(--panneau-color-border);
13
+ box-shadow: var(--panneau-shadow-lg);
14
+ outline: none;
15
+ }
16
+
17
+ /* Widened, for previews. */
18
+ .panneau-item-panel-wide {
19
+ width: min(100%, var(--panneau-item-panel-width-wide, 50%));
20
+ }
21
+
22
+ .panneau-item-panel-panel:focus-visible {
23
+ box-shadow:
24
+ var(--panneau-shadow-lg),
25
+ inset 0 0 0 var(--panneau-focus-width) var(--panneau-color-focus);
26
+ }
27
+
28
+ .panneau-item-panel-header {
29
+ display: flex;
30
+ align-items: flex-start;
31
+ justify-content: space-between;
32
+ gap: var(--panneau-space-2);
33
+ padding: var(--panneau-space-4) var(--panneau-space-4) var(--panneau-space-2);
34
+ }
35
+
36
+ .panneau-item-panel-header-buttons {
37
+ display: flex;
38
+ flex: none;
39
+ gap: var(--panneau-space-1);
40
+ }
41
+
42
+ .panneau-item-panel-title {
43
+ min-width: 0;
44
+ margin: 0;
45
+ overflow-wrap: anywhere;
46
+ }
47
+
48
+ .panneau-item-panel-actions {
49
+ display: flex;
50
+ flex-wrap: wrap;
51
+ gap: var(--panneau-space-2);
52
+ padding: 0 var(--panneau-space-4) var(--panneau-space-3);
53
+ border-bottom: var(--panneau-border-width) solid var(--panneau-color-border);
54
+ }
55
+
56
+ .panneau-item-panel-action {
57
+ display: inline-flex;
58
+ align-items: center;
59
+ gap: var(--panneau-space-1);
60
+ min-height: var(--panneau-control-height-sm);
61
+ padding: 0 var(--panneau-space-3);
62
+ border: var(--panneau-border-width) solid var(--panneau-color-border-control);
63
+ border-radius: var(--panneau-radius-md);
64
+ color: var(--panneau-color-text);
65
+ font-size: var(--panneau-font-size-sm);
66
+ font-weight: var(--panneau-font-weight-medium);
67
+ text-decoration: none;
68
+ outline: none;
69
+ }
70
+
71
+ .panneau-item-panel-action[data-hovered] {
72
+ background: var(--panneau-color-hover);
73
+ }
74
+
75
+ .panneau-item-panel-action[data-focus-visible] {
76
+ box-shadow: 0 0 0 var(--panneau-focus-width) var(--panneau-color-focus);
77
+ }
78
+
79
+ .panneau-item-panel-body {
80
+ display: flex;
81
+ flex: 1 1 auto;
82
+ flex-direction: column;
83
+ min-height: 0;
84
+ padding: var(--panneau-space-4);
85
+ overflow-y: auto;
86
+ }
87
+
88
+ .panneau-item-panel-tabs {
89
+ display: flex;
90
+ flex: 1 1 auto;
91
+ flex-direction: column;
92
+ gap: var(--panneau-space-3);
93
+ min-height: 0;
94
+ }
95
+
96
+ .panneau-item-panel-tab-panel {
97
+ display: flex;
98
+ flex: 1 1 auto;
99
+ flex-direction: column;
100
+ min-height: 0;
101
+ }
102
+
103
+ .panneau-item-panel-preview {
104
+ display: flex;
105
+ flex: 1 1 auto;
106
+ flex-direction: column;
107
+ gap: var(--panneau-space-2);
108
+ min-height: 0;
109
+ }
110
+
111
+ .panneau-item-panel-preview-link {
112
+ display: inline-flex;
113
+ align-items: center;
114
+ align-self: flex-end;
115
+ gap: var(--panneau-space-1);
116
+ border-radius: var(--panneau-radius-sm);
117
+ color: var(--panneau-color-link);
118
+ font-size: var(--panneau-font-size-sm);
119
+ text-decoration: none;
120
+ outline: none;
121
+ }
122
+
123
+ .panneau-item-panel-preview-link[data-hovered] {
124
+ text-decoration: underline;
125
+ }
126
+
127
+ .panneau-item-panel-preview-link[data-focus-visible] {
128
+ box-shadow: 0 0 0 var(--panneau-focus-width) var(--panneau-color-focus);
129
+ }
130
+
131
+ /* The page of the site, as tall as the panel allows. */
132
+ .panneau-item-panel-frame {
133
+ flex: 1 1 auto;
134
+ width: 100%;
135
+ min-height: 28rem;
136
+ border: var(--panneau-border-width) solid var(--panneau-color-border);
137
+ border-radius: var(--panneau-radius-md);
138
+ background: var(--panneau-color-surface);
139
+ }
140
+
141
+ /* From the `xl` width of the root: beside the list, in the flow of the page. */
142
+ @container panneau-root (min-width: 80rem) {
143
+ .panneau-item-panel-panel {
144
+ position: sticky;
145
+ top: var(--panneau-space-4);
146
+ z-index: auto;
147
+ width: auto;
148
+ max-height: calc(100dvh - 2 * var(--panneau-space-4));
149
+ min-height: min(40rem, calc(100dvh - 2 * var(--panneau-space-4)));
150
+ border: var(--panneau-border-width) solid var(--panneau-color-border);
151
+ border-radius: var(--panneau-item-panel-radius, var(--panneau-radius-lg));
152
+ box-shadow: var(--panneau-item-panel-shadow, var(--panneau-shadow-md));
153
+ }
154
+
155
+ .panneau-item-panel-wide {
156
+ width: auto;
157
+ }
158
+
159
+ .panneau-item-panel-panel:focus-visible {
160
+ box-shadow:
161
+ var(--panneau-item-panel-shadow, var(--panneau-shadow-md)),
162
+ 0 0 0 var(--panneau-focus-width) var(--panneau-color-focus);
163
+ }
164
+ }
@@ -0,0 +1,17 @@
1
+ import type { ResolvedResource } from '@panneau/core';
2
+ export interface ItemPanelProps {
3
+ resolved: ResolvedResource;
4
+ basePath: string;
5
+ locale: string;
6
+ id: string;
7
+ /** Where closing the panel goes: the list, with its query. */
8
+ closeHref: string;
9
+ }
10
+ /**
11
+ * The details of an item next to its list (the show route): its values with the displays of the
12
+ * lists, a link to edit it, and the `link` actions of the resource, such as its page on the site.
13
+ * A resource with a `features.preview` gets a preview tab first: a component of a plugin, or its
14
+ * page on the site. The panel can be widened, for previews, and remembers it. Escape closes it.
15
+ */
16
+ export declare function ItemPanel({ resolved, basePath, locale, id, closeHref }: ItemPanelProps): import("react").JSX.Element;
17
+ //# sourceMappingURL=ItemPanel.d.ts.map