@panneau/app 5.0.0-alpha.3 → 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
@@ -0,0 +1,162 @@
1
+ .panneau-pages-page {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--panneau-space-5);
5
+ max-width: 80rem;
6
+ margin-inline: auto;
7
+ }
8
+
9
+ .panneau-pages-narrow {
10
+ max-width: 64rem;
11
+ }
12
+
13
+ /* With the details of an item next to the list, the page takes the whole width. */
14
+ .panneau-pages-with-panel {
15
+ max-width: none;
16
+ }
17
+
18
+ .panneau-pages-split {
19
+ display: grid;
20
+ grid-template-columns: minmax(0, 1fr);
21
+ align-items: start;
22
+ gap: var(--panneau-space-4);
23
+ }
24
+
25
+ @container panneau-root (min-width: 80rem) {
26
+ .panneau-pages-with-panel .panneau-pages-split {
27
+ grid-template-columns: minmax(0, 1fr) var(--panneau-item-panel-width, 24rem);
28
+ }
29
+
30
+ /* A widened panel, for previews. */
31
+ .panneau-pages-with-panel .panneau-pages-split:has(> [data-wide]) {
32
+ grid-template-columns: minmax(0, 1fr) var(--panneau-item-panel-width-wide, 50%);
33
+ }
34
+ }
35
+
36
+ .panneau-pages-header {
37
+ display: flex;
38
+ flex-wrap: wrap;
39
+ align-items: flex-end;
40
+ justify-content: space-between;
41
+ gap: var(--panneau-space-3);
42
+ }
43
+
44
+ .panneau-pages-header-title {
45
+ display: flex;
46
+ flex-direction: column;
47
+ gap: var(--panneau-space-1);
48
+ min-width: 0;
49
+ }
50
+
51
+ .panneau-pages-header-actions {
52
+ display: flex;
53
+ flex-wrap: wrap;
54
+ gap: var(--panneau-space-2);
55
+ }
56
+
57
+ .panneau-pages-back {
58
+ display: inline-flex;
59
+ align-items: center;
60
+ gap: var(--panneau-space-1);
61
+ align-self: flex-start;
62
+ margin-inline-start: calc(-1 * var(--panneau-space-1));
63
+ padding: 0 var(--panneau-space-1);
64
+ border-radius: var(--panneau-radius-sm);
65
+ color: var(--panneau-color-text-muted);
66
+ font-size: var(--panneau-font-size-sm);
67
+ text-decoration: none;
68
+ outline: none;
69
+ }
70
+
71
+ .panneau-pages-back[data-hovered] {
72
+ color: var(--panneau-color-text);
73
+ }
74
+
75
+ .panneau-pages-back[data-focus-visible],
76
+ .panneau-pages-link-button[data-focus-visible],
77
+ .panneau-pages-card[data-focus-visible] {
78
+ box-shadow: 0 0 0 var(--panneau-focus-width) var(--panneau-color-focus);
79
+ }
80
+
81
+ .panneau-pages-link-button {
82
+ display: inline-flex;
83
+ align-items: center;
84
+ gap: var(--panneau-space-2);
85
+ min-height: var(--panneau-control-height);
86
+ padding: 0 var(--panneau-control-padding-x);
87
+ border-radius: var(--panneau-radius-md);
88
+ background: var(--panneau-color-accent);
89
+ color: var(--panneau-color-accent-text);
90
+ font-size: var(--panneau-font-size-control);
91
+ font-weight: var(--panneau-font-weight-medium);
92
+ text-decoration: none;
93
+ outline: none;
94
+ transition: background-color var(--panneau-duration);
95
+ }
96
+
97
+ .panneau-pages-link-button[data-hovered] {
98
+ background: var(--panneau-color-accent-hover);
99
+ }
100
+
101
+ .panneau-pages-intro {
102
+ margin: 0;
103
+ color: var(--panneau-color-text-muted);
104
+ }
105
+
106
+ .panneau-pages-cards {
107
+ display: grid;
108
+ grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
109
+ gap: var(--panneau-space-3);
110
+ margin: 0;
111
+ padding: 0;
112
+ list-style: none;
113
+ }
114
+
115
+ .panneau-pages-card {
116
+ display: flex;
117
+ flex-direction: column;
118
+ gap: var(--panneau-space-2);
119
+ height: 100%;
120
+ padding: var(--panneau-space-4);
121
+ border: var(--panneau-border-width) solid var(--panneau-color-border);
122
+ border-radius: var(--panneau-radius-lg);
123
+ background: var(--panneau-color-surface);
124
+ color: var(--panneau-color-text);
125
+ text-decoration: none;
126
+ outline: none;
127
+ transition:
128
+ border-color var(--panneau-duration),
129
+ box-shadow var(--panneau-duration);
130
+ }
131
+
132
+ .panneau-pages-card[data-hovered] {
133
+ border-color: var(--panneau-color-border-control);
134
+ box-shadow: var(--panneau-shadow-md);
135
+ }
136
+
137
+ .panneau-pages-card-icon {
138
+ display: inline-grid;
139
+ place-items: center;
140
+ width: 2.25rem;
141
+ height: 2.25rem;
142
+ border-radius: var(--panneau-radius-md);
143
+ background: var(--panneau-color-accent-subtle);
144
+ }
145
+
146
+ .panneau-pages-card-title {
147
+ font-weight: var(--panneau-font-weight-bold);
148
+ }
149
+
150
+ .panneau-pages-card-text {
151
+ color: var(--panneau-color-text-muted);
152
+ font-size: var(--panneau-font-size-sm);
153
+ }
154
+
155
+ .panneau-pages-center {
156
+ display: flex;
157
+ flex-direction: column;
158
+ align-items: center;
159
+ gap: var(--panneau-space-3);
160
+ padding-block: var(--panneau-space-10);
161
+ text-align: center;
162
+ }
@@ -0,0 +1,40 @@
1
+ import "./pages.css";
2
+ //#region src/pages/pages.module.css
3
+ var page = "panneau-pages-page";
4
+ var narrow = "panneau-pages-narrow";
5
+ var withPanel = "panneau-pages-with-panel";
6
+ var split = "panneau-pages-split";
7
+ var header = "panneau-pages-header";
8
+ var headerTitle = "panneau-pages-header-title";
9
+ var headerActions = "panneau-pages-header-actions";
10
+ var back = "panneau-pages-back";
11
+ var linkButton = "panneau-pages-link-button";
12
+ var card = "panneau-pages-card";
13
+ var intro = "panneau-pages-intro";
14
+ var cards = "panneau-pages-cards";
15
+ var cardIcon = "panneau-pages-card-icon";
16
+ var cardTitle = "panneau-pages-card-title";
17
+ var cardText = "panneau-pages-card-text";
18
+ var center = "panneau-pages-center";
19
+ var pages_module_default = {
20
+ page,
21
+ narrow,
22
+ withPanel,
23
+ split,
24
+ header,
25
+ headerTitle,
26
+ headerActions,
27
+ back,
28
+ linkButton,
29
+ card,
30
+ intro,
31
+ cards,
32
+ cardIcon,
33
+ cardTitle,
34
+ cardText,
35
+ center
36
+ };
37
+ //#endregion
38
+ export { back, card, cardIcon, cardText, cardTitle, cards, center, pages_module_default as default, header, headerActions, headerTitle, intro, linkButton, narrow, page, split, withPanel };
39
+
40
+ //# sourceMappingURL=pages.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.module.js","names":[],"sources":["../../src/pages/pages.module.css"],"sourcesContent":[".page {\n display: flex;\n flex-direction: column;\n gap: var(--panneau-space-5);\n max-width: 80rem;\n margin-inline: auto;\n}\n\n.narrow {\n max-width: 64rem;\n}\n\n/* With the details of an item next to the list, the page takes the whole width. */\n.withPanel {\n max-width: none;\n}\n\n.split {\n display: grid;\n grid-template-columns: minmax(0, 1fr);\n align-items: start;\n gap: var(--panneau-space-4);\n}\n\n@container panneau-root (min-width: 80rem) {\n .withPanel .split {\n grid-template-columns: minmax(0, 1fr) var(--panneau-item-panel-width, 24rem);\n }\n\n /* A widened panel, for previews. */\n .withPanel .split:has(> [data-wide]) {\n grid-template-columns: minmax(0, 1fr) var(--panneau-item-panel-width-wide, 50%);\n }\n}\n\n.header {\n display: flex;\n flex-wrap: wrap;\n align-items: flex-end;\n justify-content: space-between;\n gap: var(--panneau-space-3);\n}\n\n.headerTitle {\n display: flex;\n flex-direction: column;\n gap: var(--panneau-space-1);\n min-width: 0;\n}\n\n.headerActions {\n display: flex;\n flex-wrap: wrap;\n gap: var(--panneau-space-2);\n}\n\n.back {\n display: inline-flex;\n align-items: center;\n gap: var(--panneau-space-1);\n align-self: flex-start;\n margin-inline-start: calc(-1 * var(--panneau-space-1));\n padding: 0 var(--panneau-space-1);\n border-radius: var(--panneau-radius-sm);\n color: var(--panneau-color-text-muted);\n font-size: var(--panneau-font-size-sm);\n text-decoration: none;\n outline: none;\n}\n\n.back[data-hovered] {\n color: var(--panneau-color-text);\n}\n\n.back[data-focus-visible],\n.linkButton[data-focus-visible],\n.card[data-focus-visible] {\n box-shadow: 0 0 0 var(--panneau-focus-width) var(--panneau-color-focus);\n}\n\n.linkButton {\n display: inline-flex;\n align-items: center;\n gap: var(--panneau-space-2);\n min-height: var(--panneau-control-height);\n padding: 0 var(--panneau-control-padding-x);\n border-radius: var(--panneau-radius-md);\n background: var(--panneau-color-accent);\n color: var(--panneau-color-accent-text);\n font-size: var(--panneau-font-size-control);\n font-weight: var(--panneau-font-weight-medium);\n text-decoration: none;\n outline: none;\n transition: background-color var(--panneau-duration);\n}\n\n.linkButton[data-hovered] {\n background: var(--panneau-color-accent-hover);\n}\n\n.intro {\n margin: 0;\n color: var(--panneau-color-text-muted);\n}\n\n.cards {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));\n gap: var(--panneau-space-3);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.card {\n display: flex;\n flex-direction: column;\n gap: var(--panneau-space-2);\n height: 100%;\n padding: var(--panneau-space-4);\n border: var(--panneau-border-width) solid var(--panneau-color-border);\n border-radius: var(--panneau-radius-lg);\n background: var(--panneau-color-surface);\n color: var(--panneau-color-text);\n text-decoration: none;\n outline: none;\n transition:\n border-color var(--panneau-duration),\n box-shadow var(--panneau-duration);\n}\n\n.card[data-hovered] {\n border-color: var(--panneau-color-border-control);\n box-shadow: var(--panneau-shadow-md);\n}\n\n.cardIcon {\n display: inline-grid;\n place-items: center;\n width: 2.25rem;\n height: 2.25rem;\n border-radius: var(--panneau-radius-md);\n background: var(--panneau-color-accent-subtle);\n}\n\n.cardTitle {\n font-weight: var(--panneau-font-weight-bold);\n}\n\n.cardText {\n color: var(--panneau-color-text-muted);\n font-size: var(--panneau-font-size-sm);\n}\n\n.center {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--panneau-space-3);\n padding-block: var(--panneau-space-10);\n text-align: center;\n}\n"],"mappings":""}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * A choice of the user that the browser remembers (`localStorage`), such as a collapsed sidebar.
3
+ * `null` until the user makes it, and when the stored value isn't allowed or can't be read.
4
+ */
5
+ export declare function usePreference<T extends string>(key: string, allowed: readonly T[]): [T | null, (value: T) => void];
6
+ //# sourceMappingURL=preferences.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preferences.d.ts","sourceRoot":"","sources":["../src/preferences.ts"],"names":[],"mappings":"AAsBA;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAC1C,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,SAAS,CAAC,EAAE,GACtB,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC,CAShC"}
@@ -0,0 +1,53 @@
1
+ import { useState } from "react";
2
+ import { c } from "react/compiler-runtime";
3
+ //#region src/preferences.ts
4
+ var PREFIX = "panneau:";
5
+ function read(key, allowed) {
6
+ try {
7
+ const value = window.localStorage.getItem(`${PREFIX}${key}`);
8
+ return allowed.find((candidate) => candidate === value) ?? null;
9
+ } catch {
10
+ return null;
11
+ }
12
+ }
13
+ function write(key, value) {
14
+ try {
15
+ window.localStorage.setItem(`${PREFIX}${key}`, value);
16
+ } catch {}
17
+ }
18
+ /**
19
+ * A choice of the user that the browser remembers (`localStorage`), such as a collapsed sidebar.
20
+ * `null` until the user makes it, and when the stored value isn't allowed or can't be read.
21
+ */
22
+ function usePreference(key, allowed) {
23
+ const $ = c(8);
24
+ let t0;
25
+ if ($[0] !== allowed || $[1] !== key) {
26
+ t0 = () => read(key, allowed);
27
+ $[0] = allowed;
28
+ $[1] = key;
29
+ $[2] = t0;
30
+ } else t0 = $[2];
31
+ const [value, setValue] = useState(t0);
32
+ let t1;
33
+ if ($[3] !== key) {
34
+ t1 = (next) => {
35
+ setValue(next);
36
+ write(key, next);
37
+ };
38
+ $[3] = key;
39
+ $[4] = t1;
40
+ } else t1 = $[4];
41
+ let t2;
42
+ if ($[5] !== t1 || $[6] !== value) {
43
+ t2 = [value, t1];
44
+ $[5] = t1;
45
+ $[6] = value;
46
+ $[7] = t2;
47
+ } else t2 = $[7];
48
+ return t2;
49
+ }
50
+ //#endregion
51
+ export { usePreference };
52
+
53
+ //# sourceMappingURL=preferences.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preferences.js","names":["_c"],"sources":["../src/preferences.ts"],"sourcesContent":["import { useState } from 'react';\n\nconst PREFIX = 'panneau:';\n\nfunction read<T extends string>(key: string, allowed: readonly T[]): T | null {\n try {\n const value = window.localStorage.getItem(`${PREFIX}${key}`);\n return allowed.find((candidate) => candidate === value) ?? null;\n } catch {\n // Storage can be blocked (private windows, site data settings).\n return null;\n }\n}\n\nfunction write(key: string, value: string) {\n try {\n window.localStorage.setItem(`${PREFIX}${key}`, value);\n } catch {\n // The preference then lasts until the page is reloaded.\n }\n}\n\n/**\n * A choice of the user that the browser remembers (`localStorage`), such as a collapsed sidebar.\n * `null` until the user makes it, and when the stored value isn't allowed or can't be read.\n */\nexport function usePreference<T extends string>(\n key: string,\n allowed: readonly T[],\n): [T | null, (value: T) => void] {\n const [value, setValue] = useState<T | null>(() => read(key, allowed));\n return [\n value,\n (next: T) => {\n setValue(next);\n write(key, next);\n },\n ];\n}\n"],"mappings":";;;AAEA,IAAM,SAAS;AAEf,SAAS,KAAuB,KAAa,SAAiC;CAC1E,IAAI;EACA,MAAM,QAAQ,OAAO,aAAa,QAAQ,GAAG,SAAS,KAAK;EAC3D,OAAO,QAAQ,MAAM,cAAc,cAAc,KAAK,KAAK;CAC/D,QAAQ;EAEJ,OAAO;CACX;AACJ;AAEA,SAAS,MAAM,KAAa,OAAe;CACvC,IAAI;EACA,OAAO,aAAa,QAAQ,GAAG,SAAS,OAAO,KAAK;CACxD,QAAQ,CACJ;AAER;;;;;AAMA,SAAO,cAAA,KAAA,SAAA;CAAA,MAAA,IAAAA,EAAA,CAAA;CAAA,IAAA;CAAA,IAAA,EAAA,OAAA,WAAA,EAAA,OAAA,KAAA;EAI0C,WAAM,KAAK,KAAK,OAAO;EAAC,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CAArE,MAAA,CAAA,OAAA,YAA0B,SAAmB,EAAwB;CAAE,IAAA;CAAA,IAAA,EAAA,OAAA,KAAA;EAGnE,MAAA,SAAA;GACI,SAAS,IAAI;GACb,MAAM,KAAK,IAAI;EAAC;EACnB,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CAAA,IAAA;CAAA,IAAA,EAAA,OAAA,MAAA,EAAA,OAAA,OAAA;EALE,KAAA,CACH,OACA,EAGC;EACJ,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA,KAAA,EAAA;CAAA,OANM;AAMN"}
@@ -0,0 +1,15 @@
1
+ /** An item the user opened, for the "Go to…" palette. */
2
+ export interface RecentItem {
3
+ resource: string;
4
+ id: string;
5
+ title: string;
6
+ }
7
+ /** The items the user opened last, the most recent first. Empty when storage can't be read. */
8
+ export declare function readRecentItems(basePath: string, user: string): RecentItem[];
9
+ /** Puts an item first in the recent items, keeping the last 8. */
10
+ export declare function recordRecentItem(basePath: string, user: string, item: RecentItem): void;
11
+ /** The user the recent items belong to: the id of the definition's user. */
12
+ export declare function useRecentScope(): string;
13
+ /** Records an item as opened, once it is loaded and has a title. */
14
+ export declare function useRecordRecentItem(basePath: string, item: RecentItem | null): void;
15
+ //# sourceMappingURL=recentItems.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recentItems.d.ts","sourceRoot":"","sources":["../src/recentItems.ts"],"names":[],"mappings":"AAIA,yDAAyD;AACzD,MAAM,WAAW,UAAU;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACjB;AASD,+FAA+F;AAC/F,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CAkB5E;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,QAYhF;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,QAU5E"}
@@ -0,0 +1,70 @@
1
+ import { useEffect } from "react";
2
+ import { c } from "react/compiler-runtime";
3
+ import { usePanneau } from "@panneau/core";
4
+ //#region src/recentItems.ts
5
+ var LIMIT = 8;
6
+ /** The key of the recent items of an app (its base path) and a user, in `localStorage`. */
7
+ function storageKey(basePath, user) {
8
+ return `panneau:recent:${basePath}:${user}`;
9
+ }
10
+ /** The items the user opened last, the most recent first. Empty when storage can't be read. */
11
+ function readRecentItems(basePath, user) {
12
+ try {
13
+ const stored = JSON.parse(window.localStorage.getItem(storageKey(basePath, user)) ?? "[]");
14
+ return Array.isArray(stored) ? stored.filter((entry) => typeof entry === "object" && entry !== null && typeof entry.resource === "string" && typeof entry.id === "string" && typeof entry.title === "string") : [];
15
+ } catch {
16
+ return [];
17
+ }
18
+ }
19
+ /** Puts an item first in the recent items, keeping the last 8. */
20
+ function recordRecentItem(basePath, user, item) {
21
+ const items = [item, ...readRecentItems(basePath, user).filter((entry) => entry.resource !== item.resource || entry.id !== item.id)].slice(0, LIMIT);
22
+ try {
23
+ window.localStorage.setItem(storageKey(basePath, user), JSON.stringify(items));
24
+ } catch {}
25
+ }
26
+ /** The user the recent items belong to: the id of the definition's user. */
27
+ function useRecentScope() {
28
+ return String(usePanneau().definition?.user?.id ?? "");
29
+ }
30
+ /** Records an item as opened, once it is loaded and has a title. */
31
+ function useRecordRecentItem(basePath, item) {
32
+ const $ = c(7);
33
+ const user = useRecentScope();
34
+ const resource = item?.resource;
35
+ const id = item?.id;
36
+ const title = item?.title;
37
+ let t0;
38
+ let t1;
39
+ if ($[0] !== basePath || $[1] !== id || $[2] !== resource || $[3] !== title || $[4] !== user) {
40
+ t0 = () => {
41
+ if (resource !== void 0 && id !== void 0 && title !== void 0 && title !== "") recordRecentItem(basePath, user, {
42
+ resource,
43
+ id,
44
+ title
45
+ });
46
+ };
47
+ t1 = [
48
+ basePath,
49
+ user,
50
+ resource,
51
+ id,
52
+ title
53
+ ];
54
+ $[0] = basePath;
55
+ $[1] = id;
56
+ $[2] = resource;
57
+ $[3] = title;
58
+ $[4] = user;
59
+ $[5] = t0;
60
+ $[6] = t1;
61
+ } else {
62
+ t0 = $[5];
63
+ t1 = $[6];
64
+ }
65
+ useEffect(t0, t1);
66
+ }
67
+ //#endregion
68
+ export { readRecentItems, recordRecentItem, useRecentScope, useRecordRecentItem };
69
+
70
+ //# sourceMappingURL=recentItems.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recentItems.js","names":["_c"],"sources":["../src/recentItems.ts"],"sourcesContent":["import { useEffect } from 'react';\n\nimport { usePanneau } from '@panneau/core';\n\n/** An item the user opened, for the \"Go to…\" palette. */\nexport interface RecentItem {\n resource: string;\n id: string;\n title: string;\n}\n\nconst LIMIT = 8;\n\n/** The key of the recent items of an app (its base path) and a user, in `localStorage`. */\nfunction storageKey(basePath: string, user: string): string {\n return `panneau:recent:${basePath}:${user}`;\n}\n\n/** The items the user opened last, the most recent first. Empty when storage can't be read. */\nexport function readRecentItems(basePath: string, user: string): RecentItem[] {\n try {\n const stored: unknown = JSON.parse(\n window.localStorage.getItem(storageKey(basePath, user)) ?? '[]',\n );\n return Array.isArray(stored)\n ? stored.filter(\n (entry): entry is RecentItem =>\n typeof entry === 'object' &&\n entry !== null &&\n typeof (entry as RecentItem).resource === 'string' &&\n typeof (entry as RecentItem).id === 'string' &&\n typeof (entry as RecentItem).title === 'string',\n )\n : [];\n } catch {\n return [];\n }\n}\n\n/** Puts an item first in the recent items, keeping the last 8. */\nexport function recordRecentItem(basePath: string, user: string, item: RecentItem) {\n const items = [\n item,\n ...readRecentItems(basePath, user).filter(\n (entry) => entry.resource !== item.resource || entry.id !== item.id,\n ),\n ].slice(0, LIMIT);\n try {\n window.localStorage.setItem(storageKey(basePath, user), JSON.stringify(items));\n } catch {\n // The palette then shows no recent items.\n }\n}\n\n/** The user the recent items belong to: the id of the definition's user. */\nexport function useRecentScope(): string {\n return String(usePanneau().definition?.user?.id ?? '');\n}\n\n/** Records an item as opened, once it is loaded and has a title. */\nexport function useRecordRecentItem(basePath: string, item: RecentItem | null) {\n const user = useRecentScope();\n const resource = item?.resource;\n const id = item?.id;\n const title = item?.title;\n useEffect(() => {\n if (resource !== undefined && id !== undefined && title !== undefined && title !== '') {\n recordRecentItem(basePath, user, { resource, id, title });\n }\n }, [basePath, user, resource, id, title]);\n}\n"],"mappings":";;;;AAWA,IAAM,QAAQ;;AAGd,SAAS,WAAW,UAAkB,MAAsB;CACxD,OAAO,kBAAkB,SAAQ,GAAI;AACzC;;AAGA,SAAgB,gBAAgB,UAAkB,MAA4B;CAC1E,IAAI;EACA,MAAM,SAAkB,KAAK,MACzB,OAAO,aAAa,QAAQ,WAAW,UAAU,IAAI,CAAC,KAAK,IAC/D;EACA,OAAO,MAAM,QAAQ,MAAM,IACrB,OAAO,QACF,UACG,OAAO,UAAU,YACjB,UAAU,QACV,OAAQ,MAAqB,aAAa,YAC1C,OAAQ,MAAqB,OAAO,YACpC,OAAQ,MAAqB,UAAU,QAC/C,IACA,CAAA;CACV,QAAQ;EACJ,OAAO,CAAA;CACX;AACJ;;AAGA,SAAgB,iBAAiB,UAAkB,MAAc,MAAkB;CAC/E,MAAM,QAAQ,CACV,MACA,GAAG,gBAAgB,UAAU,IAAI,CAAC,CAAC,QAC9B,UAAU,MAAM,aAAa,KAAK,YAAY,MAAM,OAAO,KAAK,EACrE,CAAC,CACJ,CAAC,MAAM,GAAG,KAAK;CAChB,IAAI;EACA,OAAO,aAAa,QAAQ,WAAW,UAAU,IAAI,GAAG,KAAK,UAAU,KAAK,CAAC;CACjF,QAAQ,CACJ;AAER;;AAGA,SAAO,iBAAA;CAAA,OACI,OAAO,WAAW,CAAC,CAAA,YAAiB,MAAI,MAAjC,EAAuC;AAAC;;AAI1D,SAAO,oBAAA,UAAA,MAAA;CAAA,MAAA,IAAAA,EAAA,CAAA;CACH,MAAA,OAAa,eAAe;CAC5B,MAAA,WAAiB,MAAI;CACrB,MAAA,KAAW,MAAI;CACf,MAAA,QAAc,MAAI;CAAQ,IAAA;CAAA,IAAA;CAAA,IAAA,EAAA,OAAA,YAAA,EAAA,OAAA,MAAA,EAAA,OAAA,YAAA,EAAA,OAAA,SAAA,EAAA,OAAA,MAAA;EAChB,WAAA;GACN,IAAI,aAAa,KAAA,KAAa,OAAO,KAAA,KAAa,UAAU,KAAA,KAAa,UAAU,IAC/E,iBAAiB,UAAU,MAAM;IAAA;IAAA;IAAA;GAAsB,CAAC;EAC3D;EACF,KAAA;GAAC;GAAU;GAAM;GAAU;GAAI;EAAK;EAAC,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;EAAA,EAAA,KAAA;CAAA,OAAA;EAAA,KAAA,EAAA;EAAA,KAAA,EAAA;CAAA;CAJxC,UAAU,IAIP,EAAqC;AAAC"}
@@ -0,0 +1,189 @@
1
+ /* The frame of the app: the page on a panel inset from the edges, on the window's background, as
2
+ * in recent browsers. One column on a small screen, with a bar on top. From Panneau's `lg`
3
+ * breakpoint (64rem, measured on the Panneau root, a size container): the sidebar on the left. */
4
+
5
+ .panneau-app-layout-layout {
6
+ display: grid;
7
+ grid-template-rows: auto minmax(0, 1fr);
8
+ grid-template-columns: minmax(0, 1fr);
9
+ min-height: 100dvh;
10
+ background: var(--panneau-shell-bg, var(--panneau-color-surface-sunken));
11
+ }
12
+
13
+ .panneau-app-layout-top-bar {
14
+ position: sticky;
15
+ top: 0;
16
+ z-index: 1;
17
+ display: flex;
18
+ align-items: center;
19
+ gap: var(--panneau-space-2);
20
+ min-height: 3.25rem;
21
+ padding: var(--panneau-space-1) var(--panneau-space-3) var(--panneau-space-1)
22
+ var(--panneau-space-1);
23
+ background: var(--panneau-shell-bg, var(--panneau-color-surface-sunken));
24
+ }
25
+
26
+ .panneau-app-layout-top-bar-search {
27
+ margin-inline-start: auto;
28
+ }
29
+
30
+ .panneau-app-layout-top-bar-name {
31
+ overflow: hidden;
32
+ color: var(--panneau-color-text);
33
+ font-weight: var(--panneau-font-weight-bold);
34
+ text-decoration: none;
35
+ text-overflow: ellipsis;
36
+ white-space: nowrap;
37
+ outline: none;
38
+ }
39
+
40
+ .panneau-app-layout-top-bar-name[data-focus-visible] {
41
+ border-radius: var(--panneau-radius-sm);
42
+ box-shadow: 0 0 0 var(--panneau-focus-width) var(--panneau-color-focus);
43
+ }
44
+
45
+ .panneau-app-layout-sidebar {
46
+ display: none;
47
+ }
48
+
49
+ .panneau-app-layout-main {
50
+ display: flex;
51
+ flex-direction: column;
52
+ min-width: 0;
53
+ padding: 0 var(--panneau-shell-inset, var(--panneau-space-2))
54
+ var(--panneau-shell-inset, var(--panneau-space-2));
55
+ }
56
+
57
+ .panneau-app-layout-main:focus {
58
+ outline: none;
59
+ }
60
+
61
+ .panneau-app-layout-panel {
62
+ flex: 1 1 auto;
63
+ min-width: 0;
64
+ padding: var(--panneau-space-5) var(--panneau-space-4) var(--panneau-space-8);
65
+ background: var(--panneau-shell-panel-bg, var(--panneau-color-surface));
66
+ border: var(
67
+ --panneau-shell-panel-border,
68
+ var(--panneau-border-width) solid var(--panneau-color-border)
69
+ );
70
+ border-radius: var(--panneau-shell-panel-radius, calc(var(--panneau-radius-lg) * 1.5));
71
+ box-shadow: var(--panneau-shell-panel-shadow, var(--panneau-shadow-sm));
72
+ }
73
+
74
+ /* The drawer of a small screen. */
75
+ .panneau-app-layout-backdrop {
76
+ position: fixed;
77
+ inset: 0;
78
+ z-index: var(--panneau-z-overlay);
79
+ background: var(--panneau-color-backdrop);
80
+ }
81
+
82
+ .panneau-app-layout-backdrop[data-entering] {
83
+ animation: panneau-app-layout-fade var(--panneau-duration) ease-out;
84
+ }
85
+
86
+ .panneau-app-layout-drawer {
87
+ position: fixed;
88
+ inset-block: 0;
89
+ inset-inline-start: 0;
90
+ width: min(18rem, 85vw);
91
+ background: var(--panneau-shell-bg, var(--panneau-color-surface-sunken));
92
+ box-shadow: var(--panneau-shadow-lg);
93
+ }
94
+
95
+ .panneau-app-layout-drawer[data-entering] {
96
+ animation: panneau-app-layout-slide 200ms ease-out;
97
+ }
98
+
99
+ .panneau-app-layout-drawer-dialog {
100
+ height: 100%;
101
+ outline: none;
102
+ }
103
+
104
+ /* Keyboard users jump past the navigation. */
105
+ .panneau-app-layout-skip-link {
106
+ position: absolute;
107
+ top: var(--panneau-space-2);
108
+ left: var(--panneau-space-2);
109
+ z-index: 100;
110
+ padding: var(--panneau-space-2) var(--panneau-space-3);
111
+ border-radius: var(--panneau-radius-md);
112
+ background: var(--panneau-color-accent);
113
+ color: var(--panneau-color-accent-text);
114
+ font-weight: var(--panneau-font-weight-medium);
115
+ text-decoration: none;
116
+ }
117
+
118
+ .panneau-app-layout-skip-link:not(:focus) {
119
+ width: 1px;
120
+ height: 1px;
121
+ padding: 0;
122
+ overflow: hidden;
123
+ clip-path: inset(50%);
124
+ white-space: nowrap;
125
+ }
126
+
127
+ @keyframes panneau-app-layout-fade {
128
+ from {
129
+ opacity: 0;
130
+ }
131
+ }
132
+
133
+ @keyframes panneau-app-layout-slide {
134
+ from {
135
+ transform: translateX(-100%);
136
+ }
137
+ }
138
+
139
+ @container panneau-root (min-width: 64rem) {
140
+ .panneau-app-layout-layout {
141
+ grid-template-rows: minmax(0, 1fr);
142
+ grid-template-columns: var(--panneau-sidebar-width, 15rem) minmax(0, 1fr);
143
+ transition: grid-template-columns 200ms ease-out;
144
+ }
145
+
146
+ .panneau-app-layout-layout.panneau-app-layout-collapsed {
147
+ grid-template-columns: var(--panneau-sidebar-width-collapsed, 3.5rem) minmax(0, 1fr);
148
+ }
149
+
150
+ .panneau-app-layout-top-bar {
151
+ display: none;
152
+ }
153
+
154
+ .panneau-app-layout-sidebar {
155
+ position: sticky;
156
+ top: 0;
157
+ display: block;
158
+ height: 100dvh;
159
+ min-width: 0;
160
+ overflow: hidden;
161
+ background: var(--panneau-sidebar-bg, transparent);
162
+ border-inline-end: var(--panneau-sidebar-border, 0 none);
163
+ }
164
+
165
+ .panneau-app-layout-main {
166
+ padding: var(--panneau-shell-inset, var(--panneau-space-2));
167
+ padding-inline-start: 0;
168
+ }
169
+
170
+ .panneau-app-layout-panel {
171
+ min-height: calc(100dvh - 2 * var(--panneau-shell-inset, var(--panneau-space-2)));
172
+ padding: var(--panneau-space-6) var(--panneau-shell-panel-padding-x, var(--panneau-space-8))
173
+ var(--panneau-space-10);
174
+ }
175
+ }
176
+
177
+ @media (prefers-reduced-motion: reduce) {
178
+ .panneau-app-layout-layout,
179
+ .panneau-app-layout-backdrop[data-entering],
180
+ .panneau-app-layout-drawer[data-entering] {
181
+ animation: none;
182
+ transition: none;
183
+ }
184
+ }
185
+
186
+ /* The title of a page takes focus when the route changes, for screen readers: no ring. */
187
+ .panneau-app-layout-main h1[tabindex='-1']:focus {
188
+ outline: none;
189
+ }
@@ -0,0 +1,22 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { Definition } from '@panneau/protocol';
3
+ import { type AppRoute } from '../routes';
4
+ import { type ColorSchemePreference } from './Sidebar';
5
+ export interface AppLayoutProps {
6
+ definition: Definition;
7
+ basePath: string;
8
+ route: AppRoute;
9
+ colorScheme: ColorSchemePreference;
10
+ onColorSchemeChange: (scheme: ColorSchemePreference) => void;
11
+ children?: ReactNode;
12
+ }
13
+ /**
14
+ * The frame of the app. From the `lg` width of the Panneau root (64rem): the sidebar on the
15
+ * window's background and the page on a raised panel, like the vertical tabs of recent browsers.
16
+ * The sidebar collapses to a rail of icons (with its button, or Ctrl+\ / ⌘+\), and the browser
17
+ * remembers it. On a smaller screen: a bar at the top, whose button opens the sidebar as a drawer.
18
+ * When the route changes, focus moves to the title of the new page, and the page scrolls to the
19
+ * top.
20
+ */
21
+ export declare function AppLayout({ definition, basePath, route, colorScheme, onColorSchemeChange, children, }: AppLayoutProps): import("react").JSX.Element;
22
+ //# sourceMappingURL=AppLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppLayout.d.ts","sourceRoot":"","sources":["../../src/shell/AppLayout.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAA+B,MAAM,OAAO,CAAC;AAIpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAKpD,OAAO,EAAE,KAAK,QAAQ,EAAa,MAAM,WAAW,CAAC;AAGrD,OAAO,EAAE,KAAK,qBAAqB,EAAW,MAAM,WAAW,CAAC;AAKhE,MAAM,WAAW,cAAc;IAC3B,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,QAAQ,CAAC;IAChB,WAAW,EAAE,qBAAqB,CAAC;IACnC,mBAAmB,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,IAAI,CAAC;IAC7D,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AA8CD;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EACtB,UAAU,EACV,QAAQ,EACR,KAAK,EACL,WAAW,EACX,mBAAmB,EACnB,QAAQ,GACX,EAAE,cAAc,+BAyHhB"}