@tidecloak/ui-framework 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +377 -0
- package/dist/index.d.mts +2739 -0
- package/dist/index.d.ts +2739 -0
- package/dist/index.js +12869 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +12703 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +54 -0
- package/src/components/common/ActionButton.tsx +234 -0
- package/src/components/common/EmptyState.tsx +140 -0
- package/src/components/common/LoadingSkeleton.tsx +121 -0
- package/src/components/common/RefreshButton.tsx +127 -0
- package/src/components/common/StatusBadge.tsx +177 -0
- package/src/components/common/index.ts +31 -0
- package/src/components/data-table/DataTable.tsx +201 -0
- package/src/components/data-table/PaginatedTable.tsx +247 -0
- package/src/components/data-table/index.ts +2 -0
- package/src/components/dialogs/CollapsibleSection.tsx +184 -0
- package/src/components/dialogs/ConfirmDialog.tsx +264 -0
- package/src/components/dialogs/DetailDialog.tsx +228 -0
- package/src/components/dialogs/index.ts +3 -0
- package/src/components/index.ts +5 -0
- package/src/components/pages/base/ApprovalsPageBase.tsx +680 -0
- package/src/components/pages/base/LogsPageBase.tsx +581 -0
- package/src/components/pages/base/RolesPageBase.tsx +1470 -0
- package/src/components/pages/base/TemplatesPageBase.tsx +761 -0
- package/src/components/pages/base/UsersPageBase.tsx +843 -0
- package/src/components/pages/base/index.ts +58 -0
- package/src/components/pages/connected/ApprovalsPage.tsx +797 -0
- package/src/components/pages/connected/LogsPage.tsx +267 -0
- package/src/components/pages/connected/RolesPage.tsx +525 -0
- package/src/components/pages/connected/TemplatesPage.tsx +181 -0
- package/src/components/pages/connected/UsersPage.tsx +237 -0
- package/src/components/pages/connected/index.ts +36 -0
- package/src/components/pages/index.ts +5 -0
- package/src/components/tabs/TabsView.tsx +300 -0
- package/src/components/tabs/index.ts +1 -0
- package/src/components/ui/index.tsx +1001 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useAutoRefresh.ts +119 -0
- package/src/hooks/usePagination.ts +152 -0
- package/src/hooks/useSelection.ts +81 -0
- package/src/index.ts +256 -0
- package/src/theme.ts +185 -0
- package/src/tide/index.ts +19 -0
- package/src/tide/tidePolicy.ts +270 -0
- package/src/types/index.ts +484 -0
- package/src/utils/index.ts +121 -0
package/README.md
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
# @tide/ui
|
|
2
|
+
|
|
3
|
+
Ready-to-use admin pages for Tide. Manage users, roles, approvals, logs, and templates with minimal setup.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @tide/ui @tidecloak/react @tidecloak/js
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { QueryClient, QueryClientProvider, UsersPage } from '@tide/ui';
|
|
15
|
+
import { TideCloakContextProvider, useTideCloak, Authenticated, Unauthenticated } from '@tidecloak/react';
|
|
16
|
+
import tidecloakConfig from './tidecloak.json';
|
|
17
|
+
|
|
18
|
+
const queryClient = new QueryClient();
|
|
19
|
+
|
|
20
|
+
function App() {
|
|
21
|
+
return (
|
|
22
|
+
<QueryClientProvider client={queryClient}>
|
|
23
|
+
<TideCloakContextProvider config={tidecloakConfig}>
|
|
24
|
+
<Authenticated>
|
|
25
|
+
<AdminDashboard />
|
|
26
|
+
</Authenticated>
|
|
27
|
+
<Unauthenticated>
|
|
28
|
+
<LoginPage />
|
|
29
|
+
</Unauthenticated>
|
|
30
|
+
</TideCloakContextProvider>
|
|
31
|
+
</QueryClientProvider>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function AdminDashboard() {
|
|
36
|
+
const { AdminAPI } = useTideCloak();
|
|
37
|
+
|
|
38
|
+
return <UsersPage adminAPI={AdminAPI} title="Users" />;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function LoginPage() {
|
|
42
|
+
const { login } = useTideCloak();
|
|
43
|
+
|
|
44
|
+
return <button onClick={login}>Login</button>;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
That's it. You have a working user management page.
|
|
49
|
+
|
|
50
|
+
## Available Pages
|
|
51
|
+
|
|
52
|
+
### UsersPage
|
|
53
|
+
Manage users and assign roles.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
<UsersPage adminAPI={AdminAPI} />
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### RolesPage
|
|
60
|
+
Create roles with policy support and approval workflows.
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { createLocalStoragePolicyAPI, createLocalStorageTemplateAPI } from '@tide/ui';
|
|
64
|
+
|
|
65
|
+
<RolesPage
|
|
66
|
+
adminAPI={AdminAPI}
|
|
67
|
+
policyAPI={createLocalStoragePolicyAPI()}
|
|
68
|
+
templateAPI={createLocalStorageTemplateAPI()}
|
|
69
|
+
tideContext={tideContext}
|
|
70
|
+
currentUsername={username}
|
|
71
|
+
/>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### ApprovalsPage
|
|
75
|
+
Review and approve pending changes.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
<ApprovalsPage
|
|
79
|
+
adminAPI={AdminAPI}
|
|
80
|
+
tideContext={tideContext}
|
|
81
|
+
currentUsername={username}
|
|
82
|
+
/>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### LogsPage
|
|
86
|
+
View access and policy activity.
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { createLocalStoragePolicyLogsAPI } from '@tide/ui';
|
|
90
|
+
|
|
91
|
+
<LogsPage
|
|
92
|
+
adminAPI={AdminAPI}
|
|
93
|
+
policyLogsAPI={createLocalStoragePolicyLogsAPI()}
|
|
94
|
+
/>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### TemplatesPage
|
|
98
|
+
Manage policy templates.
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
import { createLocalStorageTemplateAPI } from '@tide/ui';
|
|
102
|
+
|
|
103
|
+
<TemplatesPage api={createLocalStorageTemplateAPI()} />
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Full Example
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import { StrictMode } from 'react';
|
|
110
|
+
import { createRoot } from 'react-dom/client';
|
|
111
|
+
import {
|
|
112
|
+
QueryClient, QueryClientProvider,
|
|
113
|
+
UsersPage, RolesPage, ApprovalsPage, LogsPage, TemplatesPage,
|
|
114
|
+
createLocalStorageTemplateAPI,
|
|
115
|
+
createLocalStoragePolicyAPI,
|
|
116
|
+
createLocalStorageAccessMetadataAPI,
|
|
117
|
+
createLocalStoragePolicyLogsAPI,
|
|
118
|
+
Tabs, TabsList, TabsTrigger, TabsContent,
|
|
119
|
+
} from '@tide/ui';
|
|
120
|
+
import { TideCloakContextProvider, useTideCloak, Authenticated, Unauthenticated } from '@tidecloak/react';
|
|
121
|
+
import tidecloakConfig from './tidecloak.json';
|
|
122
|
+
|
|
123
|
+
const queryClient = new QueryClient();
|
|
124
|
+
const templateAPI = createLocalStorageTemplateAPI();
|
|
125
|
+
const policyAPI = createLocalStoragePolicyAPI();
|
|
126
|
+
const accessMetadataAPI = createLocalStorageAccessMetadataAPI();
|
|
127
|
+
const policyLogsAPI = createLocalStoragePolicyLogsAPI();
|
|
128
|
+
|
|
129
|
+
function AdminDashboard() {
|
|
130
|
+
const {
|
|
131
|
+
AdminAPI,
|
|
132
|
+
logout,
|
|
133
|
+
isInitializing,
|
|
134
|
+
getValueFromIdToken,
|
|
135
|
+
initializeTideRequest,
|
|
136
|
+
getVendorId,
|
|
137
|
+
getResource,
|
|
138
|
+
approveTideRequests,
|
|
139
|
+
} = useTideCloak();
|
|
140
|
+
|
|
141
|
+
const tideContext = { initializeTideRequest, getVendorId, getResource, approveTideRequests };
|
|
142
|
+
const username = getValueFromIdToken('preferred_username') ?? undefined;
|
|
143
|
+
|
|
144
|
+
if (isInitializing) {
|
|
145
|
+
return <p>Loading...</p>;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<div>
|
|
150
|
+
<header>
|
|
151
|
+
<h1>Admin Dashboard</h1>
|
|
152
|
+
<p>Signed in as {username}</p>
|
|
153
|
+
<button onClick={logout}>Logout</button>
|
|
154
|
+
</header>
|
|
155
|
+
|
|
156
|
+
<Tabs defaultValue="users">
|
|
157
|
+
<TabsList>
|
|
158
|
+
<TabsTrigger value="users">Users</TabsTrigger>
|
|
159
|
+
<TabsTrigger value="roles">Roles</TabsTrigger>
|
|
160
|
+
<TabsTrigger value="approvals">Approvals</TabsTrigger>
|
|
161
|
+
<TabsTrigger value="logs">Logs</TabsTrigger>
|
|
162
|
+
<TabsTrigger value="templates">Templates</TabsTrigger>
|
|
163
|
+
</TabsList>
|
|
164
|
+
|
|
165
|
+
<TabsContent value="users">
|
|
166
|
+
<UsersPage adminAPI={AdminAPI} accessMetadataAPI={accessMetadataAPI} />
|
|
167
|
+
</TabsContent>
|
|
168
|
+
|
|
169
|
+
<TabsContent value="roles">
|
|
170
|
+
<RolesPage
|
|
171
|
+
adminAPI={AdminAPI}
|
|
172
|
+
templateAPI={templateAPI}
|
|
173
|
+
policyAPI={policyAPI}
|
|
174
|
+
policyLogsAPI={policyLogsAPI}
|
|
175
|
+
tideContext={tideContext}
|
|
176
|
+
currentUsername={username}
|
|
177
|
+
/>
|
|
178
|
+
</TabsContent>
|
|
179
|
+
|
|
180
|
+
<TabsContent value="approvals">
|
|
181
|
+
<ApprovalsPage
|
|
182
|
+
adminAPI={AdminAPI}
|
|
183
|
+
tideContext={tideContext}
|
|
184
|
+
accessMetadataAPI={accessMetadataAPI}
|
|
185
|
+
policyLogsAPI={policyLogsAPI}
|
|
186
|
+
currentUsername={username}
|
|
187
|
+
/>
|
|
188
|
+
</TabsContent>
|
|
189
|
+
|
|
190
|
+
<TabsContent value="logs">
|
|
191
|
+
<LogsPage adminAPI={AdminAPI} policyLogsAPI={policyLogsAPI} />
|
|
192
|
+
</TabsContent>
|
|
193
|
+
|
|
194
|
+
<TabsContent value="templates">
|
|
195
|
+
<TemplatesPage api={templateAPI} />
|
|
196
|
+
</TabsContent>
|
|
197
|
+
</Tabs>
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function LoginPage() {
|
|
203
|
+
const { login } = useTideCloak();
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<div>
|
|
207
|
+
<h1>Welcome</h1>
|
|
208
|
+
<button onClick={login}>Login</button>
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
createRoot(document.getElementById('root')!).render(
|
|
214
|
+
<StrictMode>
|
|
215
|
+
<QueryClientProvider client={queryClient}>
|
|
216
|
+
<TideCloakContextProvider config={tidecloakConfig}>
|
|
217
|
+
<Authenticated>
|
|
218
|
+
<AdminDashboard />
|
|
219
|
+
</Authenticated>
|
|
220
|
+
<Unauthenticated>
|
|
221
|
+
<LoginPage />
|
|
222
|
+
</Unauthenticated>
|
|
223
|
+
</TideCloakContextProvider>
|
|
224
|
+
</QueryClientProvider>
|
|
225
|
+
</StrictMode>
|
|
226
|
+
);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Storage Adapters
|
|
230
|
+
|
|
231
|
+
For development, use the localStorage adapters. They work instantly but show a warning that data won't persist across devices.
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
createLocalStorageTemplateAPI() // Policy templates
|
|
235
|
+
createLocalStoragePolicyAPI() // Role policies
|
|
236
|
+
createLocalStorageAccessMetadataAPI() // Change set metadata
|
|
237
|
+
createLocalStoragePolicyLogsAPI() // Policy activity logs
|
|
238
|
+
createLocalStoragePolicyApprovalsAPI() // Policy approvals
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
For production, implement your own:
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
const myPolicyAPI: PolicyAPI = {
|
|
245
|
+
getPolicy: (roleName) => fetch(`/api/policies/${roleName}`).then(r => r.json()),
|
|
246
|
+
upsertPolicy: (policy) => fetch('/api/policies', { method: 'POST', body: JSON.stringify(policy) }),
|
|
247
|
+
deletePolicy: (roleName) => fetch(`/api/policies/${roleName}`, { method: 'DELETE' }),
|
|
248
|
+
};
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Tide Enclave Integration
|
|
252
|
+
|
|
253
|
+
Pass `tideContext` to enable the Tide approval enclave popup for cryptographic signing:
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
const {
|
|
257
|
+
initializeTideRequest,
|
|
258
|
+
getVendorId,
|
|
259
|
+
getResource,
|
|
260
|
+
approveTideRequests,
|
|
261
|
+
} = useTideCloak();
|
|
262
|
+
|
|
263
|
+
const tideContext = { initializeTideRequest, getVendorId, getResource, approveTideRequests };
|
|
264
|
+
|
|
265
|
+
<ApprovalsPage tideContext={tideContext} />
|
|
266
|
+
<RolesPage tideContext={tideContext} />
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Custom Data Fetching
|
|
270
|
+
|
|
271
|
+
Need full control? Use the base components:
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
import { UsersPageBase } from '@tide/ui';
|
|
275
|
+
|
|
276
|
+
<UsersPageBase
|
|
277
|
+
fetchUsers={() => myApi.getUsers()}
|
|
278
|
+
fetchRoles={() => myApi.getRoles()}
|
|
279
|
+
onCreate={(data) => myApi.createUser(data)}
|
|
280
|
+
onUpdateProfile={(data) => myApi.updateUser(data)}
|
|
281
|
+
onUpdateRoles={(data) => myApi.updateRoles(data)}
|
|
282
|
+
onDelete={(id) => myApi.deleteUser(id)}
|
|
283
|
+
/>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Available base components: `UsersPageBase`, `RolesPageBase`, `ApprovalsPageBase`, `LogsPageBase`, `TemplatesPageBase`
|
|
287
|
+
|
|
288
|
+
## Built-in UI Components
|
|
289
|
+
|
|
290
|
+
Works out of the box with inline styles. No Tailwind or CSS required.
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
import {
|
|
294
|
+
Card, CardContent, Button, Badge, Input, Label,
|
|
295
|
+
Table, TableHeader, TableBody, TableRow, TableHead, TableCell,
|
|
296
|
+
Dialog, DialogContent, DialogHeader, DialogTitle,
|
|
297
|
+
Tabs, TabsList, TabsTrigger, TabsContent,
|
|
298
|
+
} from '@tide/ui';
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Or pass your own shadcn/ui components:
|
|
302
|
+
|
|
303
|
+
```tsx
|
|
304
|
+
import { Button } from '@/components/ui/button';
|
|
305
|
+
|
|
306
|
+
<RolesPage components={{ Button }} />
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Theming
|
|
310
|
+
|
|
311
|
+
Inject theme CSS variables for dark mode support and consistent styling:
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
import { injectThemeCSS } from '@tide/ui';
|
|
315
|
+
|
|
316
|
+
// Call once at app init
|
|
317
|
+
injectThemeCSS();
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Or use the theme constants directly:
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
import { colors, spacing, radius, focusRing } from '@tide/ui';
|
|
324
|
+
|
|
325
|
+
const myStyle = {
|
|
326
|
+
color: colors.foreground.primary,
|
|
327
|
+
padding: spacing.md,
|
|
328
|
+
borderRadius: radius.lg,
|
|
329
|
+
};
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Supports `prefers-color-scheme: dark` and `prefers-reduced-motion: reduce` automatically.
|
|
333
|
+
|
|
334
|
+
## Hooks
|
|
335
|
+
|
|
336
|
+
### useAutoRefresh
|
|
337
|
+
|
|
338
|
+
Auto-refresh data at configurable intervals with error handling:
|
|
339
|
+
|
|
340
|
+
```tsx
|
|
341
|
+
import { useAutoRefresh } from '@tide/ui';
|
|
342
|
+
|
|
343
|
+
const { secondsRemaining, refreshNow, lastError, failureCount, isStopped } = useAutoRefresh({
|
|
344
|
+
intervalSeconds: 15,
|
|
345
|
+
refresh: fetchData,
|
|
346
|
+
isBlocked: isLoading,
|
|
347
|
+
onError: (err) => console.error('Refresh failed:', err),
|
|
348
|
+
maxRetries: 3, // stops after 3 consecutive failures
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// Show countdown or error state
|
|
352
|
+
{lastError && <span>Refresh failed: {lastError.message}</span>}
|
|
353
|
+
{secondsRemaining && <span>Refreshing in {secondsRemaining}s</span>}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Accessibility
|
|
357
|
+
|
|
358
|
+
All components include proper ARIA attributes:
|
|
359
|
+
|
|
360
|
+
- `role="status"` and `aria-busy` for loading states
|
|
361
|
+
- `role="alertdialog"` with `aria-modal` for confirmation dialogs
|
|
362
|
+
- `aria-label` on interactive elements
|
|
363
|
+
- `prefers-reduced-motion` support for animations
|
|
364
|
+
|
|
365
|
+
## API Reference
|
|
366
|
+
|
|
367
|
+
| Adapter | Purpose |
|
|
368
|
+
|---------|---------|
|
|
369
|
+
| `TemplateAPI` | Store policy templates |
|
|
370
|
+
| `PolicyAPI` | Store role policies |
|
|
371
|
+
| `PolicyLogsAPI` | Log policy activity |
|
|
372
|
+
| `PolicyApprovalsAPI` | Track pending policy approvals |
|
|
373
|
+
| `AccessMetadataAPI` | Track change set metadata |
|
|
374
|
+
|
|
375
|
+
## License
|
|
376
|
+
|
|
377
|
+
MIT
|