@terpjs/react-core 0.1.0

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 (154) hide show
  1. package/README.md +190 -0
  2. package/package.json +44 -0
  3. package/src/AppShell.test.tsx +152 -0
  4. package/src/AppShell.tsx +554 -0
  5. package/src/Authorized.test.tsx +60 -0
  6. package/src/Authorized.tsx +21 -0
  7. package/src/Breadcrumbs.test.tsx +45 -0
  8. package/src/Breadcrumbs.tsx +110 -0
  9. package/src/ConfirmDialog.tsx +170 -0
  10. package/src/DetailPage.tsx +28 -0
  11. package/src/EmptyState.tsx +74 -0
  12. package/src/ErrorState.tsx +108 -0
  13. package/src/Field.test.tsx +53 -0
  14. package/src/Field.tsx +51 -0
  15. package/src/HubPage.test.tsx +108 -0
  16. package/src/HubPage.tsx +204 -0
  17. package/src/LoadingState.test.tsx +40 -0
  18. package/src/LoadingState.tsx +96 -0
  19. package/src/LoginView.test.tsx +57 -0
  20. package/src/LoginView.tsx +203 -0
  21. package/src/ModuleNav.test.tsx +96 -0
  22. package/src/ModuleNav.tsx +88 -0
  23. package/src/OverviewPage.tsx +26 -0
  24. package/src/Page.test.tsx +147 -0
  25. package/src/Page.tsx +158 -0
  26. package/src/PageActions.test.tsx +104 -0
  27. package/src/PageActions.tsx +72 -0
  28. package/src/ProfileView.test.tsx +112 -0
  29. package/src/ProfileView.tsx +84 -0
  30. package/src/RequireAuth.test.tsx +89 -0
  31. package/src/RequireAuth.tsx +22 -0
  32. package/src/ResourceList.test.tsx +176 -0
  33. package/src/ResourceList.tsx +123 -0
  34. package/src/TerpProvider.tsx +320 -0
  35. package/src/UserMenu.test.tsx +166 -0
  36. package/src/UserMenu.tsx +125 -0
  37. package/src/admin/AdminHub.tsx +108 -0
  38. package/src/admin/AuditLogAdmin.tsx +116 -0
  39. package/src/admin/GroupCreate.tsx +90 -0
  40. package/src/admin/GroupDetail.tsx +446 -0
  41. package/src/admin/GroupsAdmin.tsx +109 -0
  42. package/src/admin/UserCreate.tsx +115 -0
  43. package/src/admin/UserDetail.tsx +228 -0
  44. package/src/admin/UsersAdmin.tsx +111 -0
  45. package/src/admin/admin.test.tsx +537 -0
  46. package/src/admin/crumbs.tsx +14 -0
  47. package/src/admin/module.tsx +51 -0
  48. package/src/admin/roles.ts +19 -0
  49. package/src/bootstrap.test.tsx +67 -0
  50. package/src/bootstrap.tsx +270 -0
  51. package/src/capabilities.test.ts +24 -0
  52. package/src/capabilities.ts +26 -0
  53. package/src/createAuthClient.test.ts +176 -0
  54. package/src/createAuthClient.ts +105 -0
  55. package/src/dataview/DataView.test.tsx +392 -0
  56. package/src/dataview/DataView.tsx +467 -0
  57. package/src/dataview/DataViewCardList.tsx +189 -0
  58. package/src/dataview/DataViewColumnSettings.tsx +118 -0
  59. package/src/dataview/DataViewExpandableRow.tsx +67 -0
  60. package/src/dataview/DataViewPagination.tsx +113 -0
  61. package/src/dataview/DataViewRowActions.tsx +131 -0
  62. package/src/dataview/DataViewTable.tsx +359 -0
  63. package/src/dataview/DataViewToolbar.tsx +260 -0
  64. package/src/dataview/README.md +138 -0
  65. package/src/dataview/glyphs.tsx +175 -0
  66. package/src/dataview/hooks/hooks.test.tsx +240 -0
  67. package/src/dataview/hooks/useDataViewQuery.ts +72 -0
  68. package/src/dataview/hooks/useDataViewState.ts +310 -0
  69. package/src/dataview/hooks/useServerDataView.ts +154 -0
  70. package/src/dataview/hooks/useViewSearch.ts +68 -0
  71. package/src/dataview/index.ts +62 -0
  72. package/src/dataview/internal.tsx +96 -0
  73. package/src/dataview/repositories/HttpDataViewRepository.ts +110 -0
  74. package/src/dataview/repositories/InMemoryDataViewRepository.ts +145 -0
  75. package/src/dataview/repositories/repositories.test.ts +158 -0
  76. package/src/dataview/repositories/viewState.test.ts +90 -0
  77. package/src/dataview/repositories/viewState.ts +128 -0
  78. package/src/dataview/types.ts +249 -0
  79. package/src/errorMessages.test.tsx +83 -0
  80. package/src/errorMessages.tsx +79 -0
  81. package/src/feedback.test.tsx +167 -0
  82. package/src/files.test.tsx +142 -0
  83. package/src/files.tsx +174 -0
  84. package/src/icons.test.tsx +46 -0
  85. package/src/icons.tsx +533 -0
  86. package/src/index.ts +155 -0
  87. package/src/layout.test.tsx +72 -0
  88. package/src/layout.tsx +90 -0
  89. package/src/layoutContract.test.tsx +179 -0
  90. package/src/layoutContract.ts +137 -0
  91. package/src/locale.test.tsx +97 -0
  92. package/src/locale.tsx +246 -0
  93. package/src/nav.test.ts +21 -0
  94. package/src/nav.ts +13 -0
  95. package/src/pageMarker.ts +15 -0
  96. package/src/raw.d.ts +7 -0
  97. package/src/realtime-hook.test.tsx +226 -0
  98. package/src/realtime.test.ts +44 -0
  99. package/src/realtime.ts +307 -0
  100. package/src/refresh-session.test.tsx +114 -0
  101. package/src/revocation.test.tsx +81 -0
  102. package/src/router.test.tsx +307 -0
  103. package/src/router.tsx +222 -0
  104. package/src/sso.test.tsx +128 -0
  105. package/src/sso.ts +142 -0
  106. package/src/ssr.test.tsx +45 -0
  107. package/src/styles.test.ts +21 -0
  108. package/src/styles.ts +302 -0
  109. package/src/theme.test.tsx +74 -0
  110. package/src/theme.tsx +143 -0
  111. package/src/toast.test.tsx +94 -0
  112. package/src/toast.tsx +214 -0
  113. package/src/tokens.guard.test.ts +51 -0
  114. package/src/ui/Alert.test.tsx +19 -0
  115. package/src/ui/Alert.tsx +115 -0
  116. package/src/ui/Badge.test.tsx +14 -0
  117. package/src/ui/Badge.tsx +48 -0
  118. package/src/ui/Button.test.tsx +36 -0
  119. package/src/ui/Button.tsx +95 -0
  120. package/src/ui/Card.test.tsx +40 -0
  121. package/src/ui/Card.tsx +92 -0
  122. package/src/ui/Checkbox.test.tsx +17 -0
  123. package/src/ui/Checkbox.tsx +51 -0
  124. package/src/ui/Combobox.test.tsx +58 -0
  125. package/src/ui/Combobox.tsx +313 -0
  126. package/src/ui/DatePicker.test.tsx +60 -0
  127. package/src/ui/DatePicker.tsx +421 -0
  128. package/src/ui/Input.tsx +30 -0
  129. package/src/ui/Markdown.test.tsx +32 -0
  130. package/src/ui/Markdown.tsx +213 -0
  131. package/src/ui/Menu.test.tsx +85 -0
  132. package/src/ui/Menu.tsx +216 -0
  133. package/src/ui/Popover.tsx +218 -0
  134. package/src/ui/Radio.test.tsx +29 -0
  135. package/src/ui/Radio.tsx +127 -0
  136. package/src/ui/Select.tsx +40 -0
  137. package/src/ui/Switch.test.tsx +17 -0
  138. package/src/ui/Switch.tsx +53 -0
  139. package/src/ui/Tabs.test.tsx +29 -0
  140. package/src/ui/Tabs.tsx +128 -0
  141. package/src/ui/Textarea.tsx +27 -0
  142. package/src/ui/Tooltip.test.tsx +28 -0
  143. package/src/ui/Tooltip.tsx +67 -0
  144. package/src/ui/controlStyles.ts +9 -0
  145. package/src/uiText.test.tsx +93 -0
  146. package/src/uiText.tsx +342 -0
  147. package/src/unwrap.test.ts +67 -0
  148. package/src/unwrap.ts +101 -0
  149. package/src/useResource.test.tsx +118 -0
  150. package/src/useResource.ts +110 -0
  151. package/src/useTerpClient.test.ts +35 -0
  152. package/tsconfig.json +17 -0
  153. package/vite.config.ts +14 -0
  154. package/vitest.setup.ts +58 -0
package/src/icons.tsx ADDED
@@ -0,0 +1,533 @@
1
+ import type { CSSProperties, ReactNode } from "react";
2
+
3
+ /**
4
+ * The dependency-free icon layer: a small set of inline SVG glyphs the shell's
5
+ * navigation (and hub cards) can reference **by name** through the manifest's
6
+ * `NavItem.icon` field, with a deterministic fallback — the first letter of the
7
+ * label in a rounded tile — so a nav item without an icon still has an icon-rail
8
+ * representation. All strokes use `currentColor`, so the glyphs theme with the
9
+ * design tokens; react-core takes no icon-library dependency (an app that wants
10
+ * its own set passes any rendered node where a `ReactNode` icon slot exists).
11
+ */
12
+
13
+ const svgProps = {
14
+ width: "1.25em",
15
+ height: "1.25em",
16
+ viewBox: "0 0 24 24",
17
+ fill: "none",
18
+ stroke: "currentColor",
19
+ strokeWidth: 1.8,
20
+ strokeLinecap: "round",
21
+ strokeLinejoin: "round",
22
+ "aria-hidden": true,
23
+ focusable: false,
24
+ } as const;
25
+
26
+ /** The bundled glyphs, keyed by the names a manifest's `NavItem.icon` may use. */
27
+ export const ICON_GLYPHS: Record<string, ReactNode> = {
28
+ home: (
29
+ <svg {...svgProps}>
30
+ <path d="M3 10.5 12 3l9 7.5" />
31
+ <path d="M5.5 9.5V21h13V9.5" />
32
+ </svg>
33
+ ),
34
+ list: (
35
+ <svg {...svgProps}>
36
+ <path d="M8 6h13M8 12h13M8 18h13" />
37
+ <path d="M3.5 6h.01M3.5 12h.01M3.5 18h.01" />
38
+ </svg>
39
+ ),
40
+ folder: (
41
+ <svg {...svgProps}>
42
+ <path d="M3 6.5A1.5 1.5 0 0 1 4.5 5h4l2 2.5h8A1.5 1.5 0 0 1 20 9v9.5a1.5 1.5 0 0 1-1.5 1.5h-14A1.5 1.5 0 0 1 3 18.5Z" />
43
+ </svg>
44
+ ),
45
+ users: (
46
+ <svg {...svgProps}>
47
+ <circle cx="9" cy="8" r="3.25" />
48
+ <path d="M3.5 19.5c0-3 2.5-5 5.5-5s5.5 2 5.5 5" />
49
+ <path d="M15.5 5.5a3.25 3.25 0 0 1 0 5.5M17.5 14.8c1.8.7 3 2.2 3 4.7" />
50
+ </svg>
51
+ ),
52
+ shield: (
53
+ <svg {...svgProps}>
54
+ <path d="M12 3 5 5.5v5c0 4.5 3 8.5 7 10 4-1.5 7-5.5 7-10v-5Z" />
55
+ </svg>
56
+ ),
57
+ settings: (
58
+ <svg {...svgProps}>
59
+ <circle cx="12" cy="12" r="3" />
60
+ <path d="M12 2.75v2.5M12 18.75v2.5M2.75 12h2.5M18.75 12h2.5M5.4 5.4l1.8 1.8M16.8 16.8l1.8 1.8M18.6 5.4l-1.8 1.8M7.2 16.8l-1.8 1.8" />
61
+ </svg>
62
+ ),
63
+ sun: (
64
+ <svg {...svgProps}>
65
+ <circle cx="12" cy="12" r="4" />
66
+ <path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
67
+ </svg>
68
+ ),
69
+ moon: (
70
+ <svg {...svgProps}>
71
+ <path d="M20.2 15.2A8.5 8.5 0 0 1 8.8 3.8 8.5 8.5 0 1 0 20.2 15.2Z" />
72
+ </svg>
73
+ ),
74
+ monitor: (
75
+ <svg {...svgProps}>
76
+ <rect x="3" y="4" width="18" height="13" rx="2" />
77
+ <path d="M8 21h8M12 17v4" />
78
+ </svg>
79
+ ),
80
+ document: (
81
+ <svg {...svgProps}>
82
+ <path d="M6 3h8l4 4v14H6Z" />
83
+ <path d="M14 3v4h4" />
84
+ <path d="M9 12h6M9 16h6" />
85
+ </svg>
86
+ ),
87
+ chart: (
88
+ <svg {...svgProps}>
89
+ <path d="M4 20V4" />
90
+ <path d="M4 20h16" />
91
+ <path d="M8.5 16v-5M13 16V7.5M17.5 16v-3" />
92
+ </svg>
93
+ ),
94
+ calendar: (
95
+ <svg {...svgProps}>
96
+ <rect x="4" y="5.5" width="16" height="15" rx="1.5" />
97
+ <path d="M4 10h16M8.5 3.5v4M15.5 3.5v4" />
98
+ </svg>
99
+ ),
100
+ inbox: (
101
+ <svg {...svgProps}>
102
+ <path d="M4 5h16v14H4Z" />
103
+ <path d="M4 13h4.5l1.5 2.5h4L15.5 13H20" />
104
+ </svg>
105
+ ),
106
+ audit: (
107
+ <svg {...svgProps}>
108
+ <circle cx="10.5" cy="10.5" r="6" />
109
+ <path d="M15 15l5.5 5.5" />
110
+ <path d="M8 10.5h5M10.5 8v5" />
111
+ </svg>
112
+ ),
113
+ hub: (
114
+ <svg {...svgProps}>
115
+ <rect x="3.5" y="3.5" width="7" height="7" rx="1" />
116
+ <rect x="13.5" y="3.5" width="7" height="7" rx="1" />
117
+ <rect x="3.5" y="13.5" width="7" height="7" rx="1" />
118
+ <rect x="13.5" y="13.5" width="7" height="7" rx="1" />
119
+ </svg>
120
+ ),
121
+ plus: (
122
+ <svg {...svgProps}>
123
+ <path d="M12 5v14M5 12h14" />
124
+ </svg>
125
+ ),
126
+ edit: (
127
+ <svg {...svgProps}>
128
+ <path d="M4 20h4l10-10-4-4L4 16v4Z" />
129
+ <path d="M14 6l4 4" />
130
+ </svg>
131
+ ),
132
+ trash: (
133
+ <svg {...svgProps}>
134
+ <path d="M4 7h16" />
135
+ <path d="M9 7V4.5h6V7" />
136
+ <path d="M6 7l1 13h10l1-13" />
137
+ <path d="M10 11v6M14 11v6" />
138
+ </svg>
139
+ ),
140
+ search: (
141
+ <svg {...svgProps}>
142
+ <circle cx="11" cy="11" r="6.5" />
143
+ <path d="m16 16 4.5 4.5" />
144
+ </svg>
145
+ ),
146
+ check: (
147
+ <svg {...svgProps}>
148
+ <path d="m5 12 5 5 9-11" />
149
+ </svg>
150
+ ),
151
+ x: (
152
+ <svg {...svgProps}>
153
+ <path d="M6 6l12 12M18 6 6 18" />
154
+ </svg>
155
+ ),
156
+ "chevron-down": (
157
+ <svg {...svgProps}>
158
+ <path d="m6 9 6 6 6-6" />
159
+ </svg>
160
+ ),
161
+ "chevron-right": (
162
+ <svg {...svgProps}>
163
+ <path d="m9 6 6 6-6 6" />
164
+ </svg>
165
+ ),
166
+ "chevron-left": (
167
+ <svg {...svgProps}>
168
+ <path d="m15 6-6 6 6 6" />
169
+ </svg>
170
+ ),
171
+ "arrow-left": (
172
+ <svg {...svgProps}>
173
+ <path d="M20 12H4" />
174
+ <path d="m10 6-6 6 6 6" />
175
+ </svg>
176
+ ),
177
+ external: (
178
+ <svg {...svgProps}>
179
+ <path d="M14 4h6v6" />
180
+ <path d="M20 4 10 14" />
181
+ <path d="M20 14v5.5A1.5 1.5 0 0 1 18.5 21h-13A1.5 1.5 0 0 1 4 19.5v-13A1.5 1.5 0 0 1 5.5 5H11" />
182
+ </svg>
183
+ ),
184
+ logout: (
185
+ <svg {...svgProps}>
186
+ <path d="M14 4h4.5A1.5 1.5 0 0 1 20 5.5v13a1.5 1.5 0 0 1-1.5 1.5H14" />
187
+ <path d="M10 8 6 12l4 4" />
188
+ <path d="M6 12h11" />
189
+ </svg>
190
+ ),
191
+ user: (
192
+ <svg {...svgProps}>
193
+ <circle cx="12" cy="8" r="4" />
194
+ <path d="M4 21c0-4 3.5-7 8-7s8 3 8 7" />
195
+ </svg>
196
+ ),
197
+ bell: (
198
+ <svg {...svgProps}>
199
+ <path d="M6 17V11a6 6 0 0 1 12 0v6l1.5 2H4.5L6 17Z" />
200
+ <path d="M10 20a2 2 0 0 0 4 0" />
201
+ </svg>
202
+ ),
203
+ key: (
204
+ <svg {...svgProps}>
205
+ <circle cx="8" cy="15" r="4" />
206
+ <path d="M11 12 21 2" />
207
+ <path d="m17.5 5.5 3 3M15 8l3 3" />
208
+ </svg>
209
+ ),
210
+ globe: (
211
+ <svg {...svgProps}>
212
+ <circle cx="12" cy="12" r="9" />
213
+ <path d="M3 12h18" />
214
+ <path d="M12 3c3 3.5 3 14 0 18-3-4-3-14.5 0-18Z" />
215
+ </svg>
216
+ ),
217
+ lock: (
218
+ <svg {...svgProps}>
219
+ <rect x="4" y="10" width="16" height="11" rx="2" />
220
+ <path d="M8 10V7a4 4 0 1 1 8 0v3" />
221
+ </svg>
222
+ ),
223
+ tag: (
224
+ <svg {...svgProps}>
225
+ <path d="M3 12V4h8l10 10-8 8L3 12Z" />
226
+ <circle cx="8" cy="9" r="1.5" />
227
+ </svg>
228
+ ),
229
+ mail: (
230
+ <svg {...svgProps}>
231
+ <rect x="3" y="5" width="18" height="14" rx="2" />
232
+ <path d="m3 7 9 7 9-7" />
233
+ </svg>
234
+ ),
235
+ refresh: (
236
+ <svg {...svgProps}>
237
+ <path d="M4 10a8 8 0 0 1 14-4l2 2" />
238
+ <path d="M20 4v4h-4" />
239
+ <path d="M20 14a8 8 0 0 1-14 4l-2-2" />
240
+ <path d="M4 20v-4h4" />
241
+ </svg>
242
+ ),
243
+ filter: (
244
+ <svg {...svgProps}>
245
+ <path d="M4 5h16l-6 8v6l-4-2v-4L4 5Z" />
246
+ </svg>
247
+ ),
248
+ download: (
249
+ <svg {...svgProps}>
250
+ <path d="M12 4v12" />
251
+ <path d="m7 11 5 5 5-5" />
252
+ <path d="M4 20h16" />
253
+ </svg>
254
+ ),
255
+ upload: (
256
+ <svg {...svgProps}>
257
+ <path d="M12 20V8" />
258
+ <path d="m7 13 5-5 5 5" />
259
+ <path d="M4 4h16" />
260
+ </svg>
261
+ ),
262
+ star: (
263
+ <svg {...svgProps}>
264
+ <path d="m12 3 2.7 5.7 6.3.9-4.6 4.4 1.1 6.2L12 17.3l-5.5 2.9 1.1-6.2L3 9.6l6.3-.9L12 3Z" />
265
+ </svg>
266
+ ),
267
+ heart: (
268
+ <svg {...svgProps}>
269
+ <path d="M12 20s-7-4.5-7-10a4.5 4.5 0 0 1 7-3.5A4.5 4.5 0 0 1 19 10c0 5.5-7 10-7 10Z" />
270
+ </svg>
271
+ ),
272
+ database: (
273
+ <svg {...svgProps}>
274
+ <ellipse cx="12" cy="5.5" rx="8" ry="2.5" />
275
+ <path d="M4 5.5v13c0 1.4 3.6 2.5 8 2.5s8-1.1 8-2.5v-13" />
276
+ <path d="M4 12c0 1.4 3.6 2.5 8 2.5s8-1.1 8-2.5" />
277
+ </svg>
278
+ ),
279
+ code: (
280
+ <svg {...svgProps}>
281
+ <path d="m8 8-5 4 5 4" />
282
+ <path d="m16 8 5 4-5 4" />
283
+ <path d="m14 4-4 16" />
284
+ </svg>
285
+ ),
286
+ truck: (
287
+ <svg {...svgProps}>
288
+ <path d="M3 7h11v10H3z" />
289
+ <path d="M14 10h4l3 3v4h-7" />
290
+ <circle cx="7" cy="18" r="2" />
291
+ <circle cx="17" cy="18" r="2" />
292
+ </svg>
293
+ ),
294
+ cart: (
295
+ <svg {...svgProps}>
296
+ <path d="M3 4h2l2 12h11l2-8H7" />
297
+ <circle cx="9" cy="20" r="1.5" />
298
+ <circle cx="17" cy="20" r="1.5" />
299
+ </svg>
300
+ ),
301
+ wallet: (
302
+ <svg {...svgProps}>
303
+ <path d="M4 7a2 2 0 0 1 2-2h11v4" />
304
+ <rect x="3" y="7" width="18" height="13" rx="2" />
305
+ <circle cx="16.5" cy="13.5" r="1.2" />
306
+ </svg>
307
+ ),
308
+ "map-pin": (
309
+ <svg {...svgProps}>
310
+ <path d="M12 21s-7-6-7-12a7 7 0 0 1 14 0c0 6-7 12-7 12Z" />
311
+ <circle cx="12" cy="9" r="2.5" />
312
+ </svg>
313
+ ),
314
+ clock: (
315
+ <svg {...svgProps}>
316
+ <circle cx="12" cy="12" r="9" />
317
+ <path d="M12 7v5l3.5 2.5" />
318
+ </svg>
319
+ ),
320
+ link: (
321
+ <svg {...svgProps}>
322
+ <path d="M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1.3 1.3" />
323
+ <path d="M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1.3-1.3" />
324
+ </svg>
325
+ ),
326
+ grid: (
327
+ <svg {...svgProps}>
328
+ <rect x="4" y="4" width="7" height="7" rx="1" />
329
+ <rect x="13" y="4" width="7" height="7" rx="1" />
330
+ <rect x="4" y="13" width="7" height="7" rx="1" />
331
+ <rect x="13" y="13" width="7" height="7" rx="1" />
332
+ </svg>
333
+ ),
334
+ book: (
335
+ <svg {...svgProps}>
336
+ <path d="M5 4h11a3 3 0 0 1 3 3v13H8a3 3 0 0 1-3-3V4Z" />
337
+ <path d="M5 17a3 3 0 0 1 3-3h11" />
338
+ </svg>
339
+ ),
340
+ briefcase: (
341
+ <svg {...svgProps}>
342
+ <rect x="3" y="7" width="18" height="13" rx="2" />
343
+ <path d="M9 7V5a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2" />
344
+ <path d="M3 13h18" />
345
+ </svg>
346
+ ),
347
+ building: (
348
+ <svg {...svgProps}>
349
+ <path d="M5 21V4h14v17" />
350
+ <path d="M9 8h2M13 8h2M9 12h2M13 12h2M9 16h2M13 16h2" />
351
+ <path d="M3 21h18" />
352
+ </svg>
353
+ ),
354
+ clipboard: (
355
+ <svg {...svgProps}>
356
+ <rect x="6" y="5" width="12" height="16" rx="1.5" />
357
+ <path d="M9 5V3.5A1.5 1.5 0 0 1 10.5 2h3A1.5 1.5 0 0 1 15 3.5V5" />
358
+ </svg>
359
+ ),
360
+ layers: (
361
+ <svg {...svgProps}>
362
+ <path d="m12 3 9 5-9 5-9-5 9-5Z" />
363
+ <path d="m3 12 9 5 9-5" />
364
+ <path d="m3 17 9 5 9-5" />
365
+ </svg>
366
+ ),
367
+ send: (
368
+ <svg {...svgProps}>
369
+ <path d="M21 3 3 11l7 3 3 7 8-18Z" />
370
+ <path d="m10 14 11-11" />
371
+ </svg>
372
+ ),
373
+ phone: (
374
+ <svg {...svgProps}>
375
+ <path d="M5 4h4l2 5-2.5 1.5a11 11 0 0 0 5 5L15 13l5 2v4a2 2 0 0 1-2 2A15 15 0 0 1 3 6a2 2 0 0 1 2-2Z" />
376
+ </svg>
377
+ ),
378
+ image: (
379
+ <svg {...svgProps}>
380
+ <rect x="3" y="4" width="18" height="16" rx="2" />
381
+ <circle cx="9" cy="10" r="2" />
382
+ <path d="m4 19 6-6 4 4 3-3 4 4" />
383
+ </svg>
384
+ ),
385
+ video: (
386
+ <svg {...svgProps}>
387
+ <rect x="3" y="6" width="13" height="12" rx="2" />
388
+ <path d="m16 10 5-3v10l-5-3z" />
389
+ </svg>
390
+ ),
391
+ music: (
392
+ <svg {...svgProps}>
393
+ <path d="M9 18V5l11-2v13" />
394
+ <circle cx="7" cy="18" r="2" />
395
+ <circle cx="18" cy="16" r="2" />
396
+ </svg>
397
+ ),
398
+ wrench: (
399
+ <svg {...svgProps}>
400
+ <path d="M14.7 3.3a5 5 0 0 1 6 6L17 13v6l-3 2-2-2v-4l-8-8 3-3 8 8 3.4-3.4a3 3 0 0 0-4-4L13 6" />
401
+ </svg>
402
+ ),
403
+ zap: (
404
+ <svg {...svgProps}>
405
+ <path d="M13 3 4 14h6l-1 7 9-11h-6l1-7Z" />
406
+ </svg>
407
+ ),
408
+ };
409
+
410
+ const navIconStyle: CSSProperties = {
411
+ display: "inline-flex",
412
+ alignItems: "center",
413
+ justifyContent: "center",
414
+ width: "1.25rem",
415
+ height: "1.25rem",
416
+ flex: "0 0 1.25rem",
417
+ fontSize: "1rem",
418
+ lineHeight: 1,
419
+ };
420
+
421
+ const fallbackStyle: CSSProperties = {
422
+ display: "inline-flex",
423
+ alignItems: "center",
424
+ justifyContent: "center",
425
+ width: "100%",
426
+ height: "100%",
427
+ borderRadius: "var(--radius-sm, 4px)",
428
+ background: "var(--color-brand-primary-soft, var(--color-neutral-200))",
429
+ color: "var(--color-brand-primary, var(--color-neutral-700))",
430
+ fontSize: "0.7em",
431
+ fontWeight: "var(--font-weight-medium)" as CSSProperties["fontWeight"],
432
+ lineHeight: 1,
433
+ };
434
+
435
+ export interface NavIconProps {
436
+ /** Glyph name (a `NavItem.icon` value); unknown / missing falls back to the initial. */
437
+ name?: string;
438
+ /** The item label the fallback tile derives its initial from. */
439
+ label: string;
440
+ }
441
+
442
+ /**
443
+ * Resolve a manifest icon name to its glyph, falling back to the label's first
444
+ * letter in a tile — every nav item stays recognisable in the collapsed icon rail.
445
+ */
446
+ export function NavIcon({ name, label }: NavIconProps) {
447
+ const glyph = name !== undefined ? ICON_GLYPHS[name] : undefined;
448
+ return (
449
+ <span aria-hidden="true" data-terp="nav-icon" style={navIconStyle}>
450
+ {glyph ?? (
451
+ <span style={fallbackStyle}>
452
+ {(label[0] ?? "?").toUpperCase()}
453
+ </span>
454
+ )}
455
+ </span>
456
+ );
457
+ }
458
+
459
+ export interface IconProps {
460
+ /** Glyph name from {@link ICON_GLYPHS}; unknown names render nothing. */
461
+ name: string;
462
+ /**
463
+ * CSS length applied to width & height (default `1em`, so the glyph tracks
464
+ * the surrounding text size). All strokes use `currentColor`.
465
+ */
466
+ size?: string | number;
467
+ /** Optional accessible label; when set the glyph is exposed with `role="img"`. */
468
+ title?: string;
469
+ }
470
+
471
+ /**
472
+ * Render a bundled glyph by name — the sibling of {@link NavIcon} for use
473
+ * inside buttons, empty states, breadcrumbs and any place that needs a token-
474
+ * coloured icon without knowing the glyph inventory. `size` accepts any CSS
475
+ * length (e.g. `"1em"`, `16`, `"1.25rem"`). Unknown names render nothing —
476
+ * the caller may fall back to a text label.
477
+ */
478
+ export function Icon({ name, size = "1em", title }: IconProps) {
479
+ const glyph = ICON_GLYPHS[name];
480
+ if (glyph === undefined) {
481
+ return null;
482
+ }
483
+ const px = typeof size === "number" ? `${size}px` : size;
484
+ const labelled = title !== undefined;
485
+ return (
486
+ <span
487
+ aria-hidden={labelled ? undefined : true}
488
+ role={labelled ? "img" : undefined}
489
+ aria-label={labelled ? title : undefined}
490
+ style={{
491
+ display: "inline-flex",
492
+ alignItems: "center",
493
+ justifyContent: "center",
494
+ width: px,
495
+ height: px,
496
+ lineHeight: 1,
497
+ color: "inherit",
498
+ }}
499
+ >
500
+ {glyph}
501
+ </span>
502
+ );
503
+ }
504
+
505
+ /**
506
+ * The placeholder product mark: an abstract, token-coloured tile the shell shows
507
+ * at the top of the sidebar until an app supplies its own `logo` (any rendered
508
+ * node — an `<img>`, an inline SVG). Deliberately generic, so a generated app
509
+ * looks intentional on day one and the replacement seam is obvious.
510
+ */
511
+ export function TerpMark() {
512
+ return (
513
+ <svg
514
+ width="28"
515
+ height="28"
516
+ viewBox="0 0 28 28"
517
+ role="img"
518
+ aria-hidden="true"
519
+ focusable={false}
520
+ >
521
+ <rect width="28" height="28" rx="7" fill="var(--color-brand-primary)" />
522
+ <path
523
+ d="M7 10.5 14 6l7 4.5M9 12.5V21h10v-8.5"
524
+ fill="none"
525
+ stroke="var(--color-brand-primary-contrast)"
526
+ strokeWidth="1.8"
527
+ strokeLinecap="round"
528
+ strokeLinejoin="round"
529
+ />
530
+ </svg>
531
+ );
532
+ }
533
+
package/src/index.ts ADDED
@@ -0,0 +1,155 @@
1
+ // Public surface of @terpjs/react-core — see README.md for the component catalog and
2
+ // usage conventions: the provider/hooks that wire a tree to @terpjs/contract, the
3
+ // capability gate, the authorization guard, the TanStack Router adapter, and the
4
+ // presentational shell + token-only UI primitives.
5
+ export { TerpProvider, useAuth, useTerpClient } from "./TerpProvider";
6
+ export type { TerpProviderProps } from "./TerpProvider";
7
+ export { canPerform, DEFAULT_RANK_THRESHOLDS } from "./capabilities";
8
+ export type { RankThresholds } from "./capabilities";
9
+ export { createAuthClient } from "./createAuthClient";
10
+ export type { AuthClientOptions, TokenGetter } from "./createAuthClient";
11
+ export { Authorized, useCan } from "./Authorized";
12
+ export type { AuthorizedProps } from "./Authorized";
13
+ export { useResource } from "./useResource";
14
+ export type { Resource, ResourceSource } from "./useResource";
15
+ export { useRealtimeChannel } from "./realtime";
16
+ export type {
17
+ RealtimeChannelOptions,
18
+ RealtimeChannelState,
19
+ RealtimeStatus,
20
+ RealtimeTransport,
21
+ } from "./realtime";
22
+ export { unwrap, ApiError } from "./unwrap";
23
+ export type { FetchResult } from "./unwrap";
24
+ export { ResourceList } from "./ResourceList";
25
+ export type { ResourceListProps } from "./ResourceList";
26
+ export { RequireAuth } from "./RequireAuth";
27
+ export type { RequireAuthProps } from "./RequireAuth";
28
+ export { visibleNav } from "./nav";
29
+ export {
30
+ AppShell,
31
+ NAV_LINK_STYLE,
32
+ NAV_LINK_COLLAPSED_STYLE,
33
+ NAV_LINK_ACTIVE_STYLE,
34
+ SIDEBAR_STORAGE_KEY,
35
+ } from "./AppShell";
36
+ export type {
37
+ AppShellProps,
38
+ AppShellSlotContext,
39
+ AppShellLinkContext,
40
+ RenderBrandLink,
41
+ } from "./AppShell";
42
+ export { Icon, NavIcon, TerpMark, ICON_GLYPHS } from "./icons";
43
+ export type { IconProps, NavIconProps } from "./icons";
44
+ export { ProfileView } from "./ProfileView";
45
+ export { Breadcrumbs } from "./Breadcrumbs";
46
+ export type { BreadcrumbItem, BreadcrumbsProps, RenderBreadcrumbLink } from "./Breadcrumbs";
47
+ export { Page } from "./Page";
48
+ export type { PageProps } from "./Page";
49
+ export { LAYOUT_CONTRACTS } from "./layoutContract";
50
+ export type { LayoutContractSpec, LayoutSlotSpec } from "./layoutContract";
51
+ export { PageActions } from "./PageActions";
52
+ export type { OverflowAction, PageActionsProps } from "./PageActions";
53
+ export { ModuleNav } from "./ModuleNav";
54
+ export type { ModuleNavProps, ModuleNavTab } from "./ModuleNav";
55
+ export { OverviewPage } from "./OverviewPage";
56
+ export type { OverviewPageProps } from "./OverviewPage";
57
+ export { DetailPage } from "./DetailPage";
58
+ export type { DetailPageProps } from "./DetailPage";
59
+ export { HubPage, HubCard } from "./HubPage";
60
+ export type { HubPageProps, HubCardProps, RenderHubCardLink } from "./HubPage";
61
+ export { UiTextProvider, useStrings, useUiText, resolveUiText, DEFAULT_STRINGS } from "./uiText";
62
+ export type { UiText, ResolveUiText, TerpStrings, UiTextProviderProps } from "./uiText";
63
+ export { EmptyState } from "./EmptyState";
64
+ export type { EmptyStateProps } from "./EmptyState";
65
+ export { ErrorState, describeError } from "./ErrorState";
66
+ export type { ErrorStateProps } from "./ErrorState";
67
+ export { ErrorMessagesProvider, useErrorMessage, DEFAULT_ERROR_MESSAGES } from "./errorMessages";
68
+ export type { ErrorMessages, ErrorMessagesProviderProps } from "./errorMessages";
69
+ export { ConfirmDialog } from "./ConfirmDialog";
70
+ export type { ConfirmDialogProps } from "./ConfirmDialog";
71
+ export { LoadingState, InlineSpinner } from "./LoadingState";
72
+ export type { LoadingStateProps, InlineSpinnerProps } from "./LoadingState";
73
+ export { ToastProvider, useToast } from "./toast";
74
+ export type { ToastApi, ToastOptions, ToastProviderProps, ToastVariant } from "./toast";
75
+ export { Button } from "./ui/Button";
76
+ export type { ButtonProps, ButtonVariant } from "./ui/Button";
77
+ export { Input } from "./ui/Input";
78
+ export type { InputProps } from "./ui/Input";
79
+ export { Select } from "./ui/Select";
80
+ export type { SelectProps } from "./ui/Select";
81
+ export { Textarea } from "./ui/Textarea";
82
+ export type { TextareaProps } from "./ui/Textarea";
83
+ export { Popover } from "./ui/Popover";
84
+ export type { PopoverAlign, PopoverPlacement, PopoverProps } from "./ui/Popover";
85
+ export { Menu, MenuItem } from "./ui/Menu";
86
+ export type { MenuItemProps, MenuProps } from "./ui/Menu";
87
+ export { Combobox } from "./ui/Combobox";
88
+ export type { ComboboxOption, ComboboxProps } from "./ui/Combobox";
89
+ export { DatePicker, DateRangePicker } from "./ui/DatePicker";
90
+ export type { DatePickerProps, DateRangePickerProps, DateRangeValue } from "./ui/DatePicker";
91
+ export { Checkbox } from "./ui/Checkbox";
92
+ export type { CheckboxProps } from "./ui/Checkbox";
93
+ export { Radio, RadioGroup } from "./ui/Radio";
94
+ export type { RadioGroupProps, RadioOption, RadioProps } from "./ui/Radio";
95
+ export { Switch } from "./ui/Switch";
96
+ export type { SwitchProps } from "./ui/Switch";
97
+ export { Tabs } from "./ui/Tabs";
98
+ export type { TabItem, TabsProps } from "./ui/Tabs";
99
+ export { Badge } from "./ui/Badge";
100
+ export type { BadgeProps, BadgeTone } from "./ui/Badge";
101
+ export { Card } from "./ui/Card";
102
+ export type { CardProps } from "./ui/Card";
103
+ export { Tooltip } from "./ui/Tooltip";
104
+ export type { TooltipProps } from "./ui/Tooltip";
105
+ export { Alert } from "./ui/Alert";
106
+ export type { AlertProps, AlertTone } from "./ui/Alert";
107
+ export { Markdown } from "./ui/Markdown";
108
+ export type { MarkdownProps } from "./ui/Markdown";
109
+ export { Field } from "./Field";
110
+ export type { FieldProps } from "./Field";
111
+ export { Stack, DetailList } from "./layout";
112
+ export type { StackProps, DetailListProps, DetailItem, SpaceToken } from "./layout";
113
+ export { buildAppRouter, DEFAULT_ROLE_RANKS, PROFILE_PATH } from "./router";
114
+ export type { BuildAppRouterOptions } from "./router";
115
+ export { LoginView } from "./LoginView";
116
+ export type { DevCredentials, LoginViewProps } from "./LoginView";
117
+ export {
118
+ DEFAULT_SSO_CALLBACK_PATH,
119
+ fetchSsoAuthorizationUrl,
120
+ completeSsoCallback,
121
+ isSsoCallbackLocation,
122
+ parseSsoCallback,
123
+ } from "./sso";
124
+ export type { SsoCallbackParams, SsoProvider } from "./sso";
125
+ export { useSso } from "./TerpProvider";
126
+ export type { SsoSession } from "./TerpProvider";
127
+ export { FileUpload, useFileDownload, uploadFile, fetchFileContent } from "./files";
128
+ export type { FileMeta, FileUploadProps } from "./files";
129
+ export { collectModules, renderTerpApp, withAdminArea } from "./bootstrap";
130
+ export type { AdminAreaSections, RenderTerpAppOptions, TerpModule } from "./bootstrap";
131
+ export { adminModule } from "./admin/module";
132
+ export { AdminHub } from "./admin/AdminHub";
133
+ export { UsersAdmin } from "./admin/UsersAdmin";
134
+ export { UserCreate } from "./admin/UserCreate";
135
+ export { UserDetail } from "./admin/UserDetail";
136
+ export { GroupsAdmin } from "./admin/GroupsAdmin";
137
+ export { GroupCreate } from "./admin/GroupCreate";
138
+ export { GroupDetail } from "./admin/GroupDetail";
139
+ export { AuditLogAdmin } from "./admin/AuditLogAdmin";
140
+ export { ThemeProvider, ThemeToggle, useTheme, THEME_STORAGE_KEY } from "./theme";
141
+ export type { Theme, ThemeProviderProps, ThemeToggleProps } from "./theme";
142
+ export {
143
+ LocaleProvider,
144
+ LanguageSwitcher,
145
+ useLocale,
146
+ LOCALE_EN,
147
+ LOCALE_NL,
148
+ LOCALE_STORAGE_KEY,
149
+ } from "./locale";
150
+ export type { LocaleCatalog, LocaleProviderProps, LanguageSwitcherProps } from "./locale";
151
+ export { UserMenu, userInitials } from "./UserMenu";
152
+ export type { UserMenuProps } from "./UserMenu";
153
+
154
+ // DataView — the repository-driven data-collection surface (see src/dataview/README.md).
155
+ export * from "./dataview";