@owlmeans/web-panel 0.1.2 → 0.1.3

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 (139) hide show
  1. package/README.md +88 -1160
  2. package/build/@/components/ui/alert.d.ts +10 -0
  3. package/build/@/components/ui/alert.d.ts.map +1 -0
  4. package/build/@/components/ui/alert.js +26 -0
  5. package/build/@/components/ui/alert.js.map +1 -0
  6. package/build/@/components/ui/button.d.ts +11 -0
  7. package/build/@/components/ui/button.d.ts.map +1 -0
  8. package/build/@/components/ui/button.js +32 -0
  9. package/build/@/components/ui/button.js.map +1 -0
  10. package/build/@/components/ui/card.d.ts +10 -0
  11. package/build/@/components/ui/card.d.ts.map +1 -0
  12. package/build/@/components/ui/card.js +25 -0
  13. package/build/@/components/ui/card.js.map +1 -0
  14. package/build/@/components/ui/input.d.ts +4 -0
  15. package/build/@/components/ui/input.d.ts.map +1 -0
  16. package/build/@/components/ui/input.js +7 -0
  17. package/build/@/components/ui/input.js.map +1 -0
  18. package/build/@/components/ui/label.d.ts +5 -0
  19. package/build/@/components/ui/label.d.ts.map +1 -0
  20. package/build/@/components/ui/label.js +8 -0
  21. package/build/@/components/ui/label.js.map +1 -0
  22. package/build/@/components/ui/progress.d.ts +5 -0
  23. package/build/@/components/ui/progress.d.ts.map +1 -0
  24. package/build/@/components/ui/progress.js +11 -0
  25. package/build/@/components/ui/progress.js.map +1 -0
  26. package/build/@/components/ui/separator.d.ts +5 -0
  27. package/build/@/components/ui/separator.d.ts.map +1 -0
  28. package/build/@/components/ui/separator.js +8 -0
  29. package/build/@/components/ui/separator.js.map +1 -0
  30. package/build/@/lib/utils.d.ts +3 -0
  31. package/build/@/lib/utils.d.ts.map +1 -0
  32. package/build/@/lib/utils.js +6 -0
  33. package/build/@/lib/utils.js.map +1 -0
  34. package/build/auth/exports.d.ts +4 -4
  35. package/build/auth/exports.d.ts.map +1 -1
  36. package/build/auth/exports.js +2 -2
  37. package/build/auth/exports.js.map +1 -1
  38. package/build/auth/modules.d.ts +1 -1
  39. package/build/auth/modules.d.ts.map +1 -1
  40. package/build/auth/plugins/basic-ed25519.js +2 -2
  41. package/build/auth/plugins/basic-ed25519.js.map +1 -1
  42. package/build/auth/plugins/re-captcha.d.ts.map +1 -1
  43. package/build/auth/plugins/re-captcha.js +4 -12
  44. package/build/auth/plugins/re-captcha.js.map +1 -1
  45. package/build/auth/plugins/tunnel-consumer.d.ts.map +1 -1
  46. package/build/auth/plugins/tunnel-consumer.js +28 -16
  47. package/build/auth/plugins/tunnel-consumer.js.map +1 -1
  48. package/build/components/block.d.ts.map +1 -1
  49. package/build/components/block.js +5 -8
  50. package/build/components/block.js.map +1 -1
  51. package/build/components/button/selector.d.ts.map +1 -1
  52. package/build/components/button/selector.js +1 -2
  53. package/build/components/button/selector.js.map +1 -1
  54. package/build/components/form/button/component.d.ts +1 -1
  55. package/build/components/form/button/component.d.ts.map +1 -1
  56. package/build/components/form/button/component.js +39 -8
  57. package/build/components/form/button/component.js.map +1 -1
  58. package/build/components/form/component.d.ts.map +1 -1
  59. package/build/components/form/component.js +10 -13
  60. package/build/components/form/component.js.map +1 -1
  61. package/build/components/form/text/component.d.ts +1 -1
  62. package/build/components/form/text/component.d.ts.map +1 -1
  63. package/build/components/form/text/component.js +23 -24
  64. package/build/components/form/text/component.js.map +1 -1
  65. package/build/components/form/types.d.ts +3 -2
  66. package/build/components/form/types.d.ts.map +1 -1
  67. package/build/components/helper.d.ts +11 -2
  68. package/build/components/helper.d.ts.map +1 -1
  69. package/build/components/helper.js +50 -40
  70. package/build/components/helper.js.map +1 -1
  71. package/build/components/layout/component.d.ts.map +1 -1
  72. package/build/components/layout/component.js +2 -3
  73. package/build/components/layout/component.js.map +1 -1
  74. package/build/components/layout/types.d.ts +3 -1
  75. package/build/components/layout/types.d.ts.map +1 -1
  76. package/build/components/link.d.ts.map +1 -1
  77. package/build/components/link.js +6 -4
  78. package/build/components/link.js.map +1 -1
  79. package/build/components/panel-app/component.d.ts.map +1 -1
  80. package/build/components/panel-app/component.js +4 -7
  81. package/build/components/panel-app/component.js.map +1 -1
  82. package/build/components/panel-app/types.d.ts +6 -2
  83. package/build/components/panel-app/types.d.ts.map +1 -1
  84. package/build/components/status.d.ts.map +1 -1
  85. package/build/components/status.js +18 -7
  86. package/build/components/status.js.map +1 -1
  87. package/build/components/text.d.ts.map +1 -1
  88. package/build/components/text.js +26 -3
  89. package/build/components/text.js.map +1 -1
  90. package/build/components/types.d.ts +18 -10
  91. package/build/components/types.d.ts.map +1 -1
  92. package/build/components/uploader/image.d.ts.map +1 -1
  93. package/build/components/uploader/image.js +11 -19
  94. package/build/components/uploader/image.js.map +1 -1
  95. package/build/exports.d.ts +3 -3
  96. package/build/exports.d.ts.map +1 -1
  97. package/build/exports.js +2 -2
  98. package/build/exports.js.map +1 -1
  99. package/build/main.d.ts +4 -2
  100. package/build/main.d.ts.map +1 -1
  101. package/build/main.js +4 -4
  102. package/build/main.js.map +1 -1
  103. package/build/modules.d.ts +1 -1
  104. package/build/modules.d.ts.map +1 -1
  105. package/components.json +21 -0
  106. package/package.json +53 -34
  107. package/src/@/components/ui/alert.tsx +70 -0
  108. package/src/@/components/ui/button.tsx +60 -0
  109. package/src/@/components/ui/card.tsx +93 -0
  110. package/src/@/components/ui/input.tsx +22 -0
  111. package/src/@/components/ui/label.tsx +23 -0
  112. package/src/@/components/ui/progress.tsx +44 -0
  113. package/src/@/components/ui/separator.tsx +27 -0
  114. package/src/@/globals.css +64 -0
  115. package/src/@/lib/utils.ts +6 -0
  116. package/src/auth/exports.ts +4 -4
  117. package/src/auth/plugins/basic-ed25519.tsx +2 -2
  118. package/src/auth/plugins/re-captcha.tsx +14 -22
  119. package/src/auth/plugins/tunnel-consumer.tsx +32 -24
  120. package/src/components/block.tsx +10 -14
  121. package/src/components/button/selector.tsx +9 -9
  122. package/src/components/form/button/component.tsx +54 -14
  123. package/src/components/form/component.tsx +23 -24
  124. package/src/components/form/text/component.tsx +39 -30
  125. package/src/components/form/types.ts +4 -3
  126. package/src/components/helper.ts +56 -42
  127. package/src/components/layout/component.tsx +2 -3
  128. package/src/components/layout/types.ts +3 -1
  129. package/src/components/link.tsx +17 -7
  130. package/src/components/panel-app/component.tsx +5 -9
  131. package/src/components/panel-app/types.ts +6 -2
  132. package/src/components/status.tsx +20 -9
  133. package/src/components/text.tsx +28 -9
  134. package/src/components/types.ts +22 -10
  135. package/src/components/uploader/image.tsx +23 -23
  136. package/src/exports.ts +3 -3
  137. package/src/main.tsx +8 -5
  138. package/tests/smoke.spec.ts +24 -0
  139. package/tsconfig.json +9 -11
package/README.md CHANGED
@@ -1,1195 +1,123 @@
1
1
  # @owlmeans/web-panel
2
2
 
3
- Web-based panel components and infrastructure for OwlMeans Common Libraries. This package extends the `@owlmeans/client-panel` functionality with React web components, Material-UI integration, and web-specific implementations for building comprehensive administrative interfaces and user panels.
3
+ shadcn UI + Tailwind v4 implementation of the OwlMeans web panel layer.
4
+ Wraps the headless `@owlmeans/client-panel` logic with shadcn primitives
5
+ instead of Material UI.
4
6
 
5
- ## Overview
7
+ ## When to use this package
6
8
 
7
- The `@owlmeans/web-panel` package serves as the web-specific implementation of the OwlMeans panel system, designed for fullstack applications with focus on security and modern web user interfaces. It provides:
9
+ - New OwlMeans web apps where the consumer owns its shadcn primitives and
10
+ Tailwind v4 theme.
11
+ - Drop-in replacement for the previous Material-UI based `@owlmeans/web-panel`
12
+ (which has been renamed to [`@owlmeans/mui-panel`](../mui-panel) for
13
+ backwards-compatibility consumers).
8
14
 
9
- - **Material-UI Integration**: Complete Material Design component system with theming
10
- - **Web Panel Components**: Comprehensive React components for building web admin panels
11
- - **Authentication Integration**: Built-in authentication components and flows
12
- - **Form Management**: Advanced form components with validation and error handling
13
- - **File Upload**: File and media upload components with progress tracking
14
- - **Layout System**: Flexible layout components for organizing panel content
15
- - **Theme Customization**: Material-UI theme integration with customizable styling
16
- - **Internationalization**: Built-in i18n support with browser language detection
15
+ ## Install
17
16
 
18
- This package follows the OwlMeans "quadra" pattern as the **web** implementation, complementing:
19
- - **@owlmeans/client-panel**: Common panel declarations and base functionality *(base package)*
20
- - **@owlmeans/native-panel**: React Native panel implementation
21
- - **@owlmeans/web-panel**: Web browser panel implementation *(this package)*
22
-
23
- ## Installation
24
-
25
- ```bash
26
- npm install @owlmeans/web-panel
27
- ```
28
-
29
- ### Peer Dependencies
30
-
31
- This package requires several peer dependencies for web development:
32
-
33
- ```bash
34
- npm install react react-dom @mui/material @emotion/react @emotion/styled react-hook-form @hookform/resolvers ajv
35
- ```
36
-
37
- ## Dependencies
38
-
39
- This package builds upon the OwlMeans ecosystem and web technologies:
40
- - `@owlmeans/client-panel`: Base panel functionality and context
41
- - `@owlmeans/web-client`: Web client framework and rendering
42
- - `@owlmeans/client-auth`: Authentication components and services
43
- - `@owlmeans/client-flow`: Flow management integration
44
- - `@owlmeans/web-flow`: Web-specific flow implementations
45
- - Material-UI: React component library for Material Design
46
- - React Hook Form: Form management and validation
47
-
48
- ## Key Concepts
49
-
50
- ### Material-UI Integration
51
- Built on Material-UI (MUI) providing:
52
- - **Material Design**: Modern Material Design 3 component system
53
- - **Theme System**: Comprehensive theming with custom color schemes
54
- - **Responsive Design**: Mobile-first responsive components
55
- - **Accessibility**: Built-in accessibility support and ARIA compliance
56
-
57
- ### Panel Architecture
58
- Structured panel system with:
59
- - **Panel Context**: Shared context for panel-wide settings and state
60
- - **Authentication Guards**: Built-in authentication and authorization
61
- - **Layout Management**: Flexible layout system for complex interfaces
62
- - **Navigation Integration**: Integration with React Router and client routing
63
-
64
- ### Form Management
65
- Advanced form capabilities with:
66
- - **React Hook Form**: Performance-optimized form management
67
- - **AJV Validation**: Schema-based validation using JSON Schema
68
- - **Error Handling**: Comprehensive error display and user feedback
69
- - **File Uploads**: Integrated file and media upload components
70
-
71
- ## API Reference
72
-
73
- ### Main Functions
74
-
75
- #### `render<C, T>(context, theme?, opts?): void`
76
-
77
- Main rendering function for web panel applications.
78
-
79
- ```typescript
80
- function render<C extends ClientConfig, T extends ClientContext<C>>(
81
- context: T,
82
- theme?: Theme,
83
- opts?: RenderOptions
84
- ): void
85
- ```
86
-
87
- **Parameters:**
88
- - `context`: Client context with configuration and services
89
- - `theme`: Optional Material-UI theme customization
90
- - `opts`: Optional rendering options
91
-
92
- **Usage:**
93
- ```typescript
94
- import { render } from '@owlmeans/web-panel'
95
- import { makeWebContext } from '@owlmeans/web-client'
96
- import { createTheme } from '@mui/material/styles'
97
-
98
- const context = makeWebContext(config)
99
- const theme = createTheme({
100
- palette: {
101
- primary: { main: '#1976d2' },
102
- secondary: { main: '#dc004e' }
103
- }
104
- })
105
-
106
- render(context, theme, { rootId: 'app' })
107
- ```
108
-
109
- ### Core Components
110
-
111
- #### `PanelApp`
112
-
113
- Main panel application wrapper with Material-UI theming and context integration.
114
-
115
- ```typescript
116
- interface PanelAppProps {
117
- context: AppContext<any> // Application context
118
- provide?: ProvideFunction // Custom provider function
119
- children?: React.ReactNode // Panel content
120
- theme?: Theme // Material-UI theme
121
- }
122
-
123
- const PanelApp: FC<PanelAppProps> = (props) => { /* ... */ }
124
- ```
125
-
126
- **Features:**
127
- - Material-UI theme provider integration
128
- - CSS baseline normalization
129
- - I18n context with browser language detection
130
- - OwlMeans client app wrapper
131
-
132
- **Usage:**
133
- ```typescript
134
- import { PanelApp } from '@owlmeans/web-panel'
135
-
136
- <PanelApp context={context} theme={customTheme}>
137
- <AdminInterface />
138
- </PanelApp>
139
- ```
140
-
141
- ### Layout Components
142
-
143
- #### Panel Layout System
144
-
145
- Flexible layout components for organizing panel content.
146
-
147
- ```typescript
148
- import {
149
- PanelLayout,
150
- PanelHeader,
151
- PanelSidebar,
152
- PanelContent,
153
- PanelFooter
154
- } from '@owlmeans/web-panel'
155
-
156
- <PanelLayout>
157
- <PanelHeader title="Administration Panel">
158
- <UserMenu />
159
- </PanelHeader>
160
-
161
- <PanelSidebar>
162
- <NavigationMenu />
163
- </PanelSidebar>
164
-
165
- <PanelContent>
166
- <MainContent />
167
- </PanelContent>
168
-
169
- <PanelFooter>
170
- <StatusBar />
171
- </PanelFooter>
172
- </PanelLayout>
17
+ ```sh
18
+ bun add @owlmeans/web-panel
173
19
  ```
174
20
 
175
- ### Form Components
21
+ Peer requirements (the consuming app provides these): `react`, `react-dom`,
22
+ `react-hook-form`, `ajv`, `tailwindcss@^4`, `lucide-react`, `clsx`,
23
+ `tailwind-merge`, `class-variance-authority`, plus the radix primitives
24
+ listed in `peerDependencies`.
176
25
 
177
- #### Advanced Form System
26
+ ## Consumer setup — the `@` contract
178
27
 
179
- React Hook Form integration with Material-UI components and AJV validation.
180
-
181
- ```typescript
182
- import {
183
- PanelForm,
184
- FormField,
185
- FormButton,
186
- FormSelect,
187
- FormCheckbox,
188
- FormValidation
189
- } from '@owlmeans/web-panel'
28
+ This package imports its shadcn primitives as `@/components/ui/<name>` and
29
+ its utility as `@/lib/utils`. Build emits these specifiers verbatim
30
+ (TypeScript `moduleResolution: Bundler`). The consumer's bundler must
31
+ resolve `@/*` to its own shadcn primitive copy.
190
32
 
191
- interface UserFormData {
192
- name: string
193
- email: string
194
- role: string
195
- active: boolean
196
- }
33
+ ### 1. Add the package's primitives to your app
197
34
 
198
- const userSchema = {
199
- type: 'object',
200
- properties: {
201
- name: { type: 'string', minLength: 2 },
202
- email: { type: 'string', format: 'email' },
203
- role: { type: 'string', enum: ['admin', 'user'] },
204
- active: { type: 'boolean' }
205
- },
206
- required: ['name', 'email', 'role']
207
- }
35
+ Generate the matching primitives in your app once. You can either copy them
36
+ from this package's `src/@/components/ui/` or use the shadcn CLI:
208
37
 
209
- <PanelForm<UserFormData>
210
- schema={userSchema}
211
- onSubmit={handleSubmit}
212
- defaultValues={defaultValues}
213
- >
214
- <FormField
215
- name="name"
216
- label="Full Name"
217
- type="text"
218
- required
219
- />
220
-
221
- <FormField
222
- name="email"
223
- label="Email Address"
224
- type="email"
225
- required
226
- />
227
-
228
- <FormSelect
229
- name="role"
230
- label="User Role"
231
- options={[
232
- { value: 'admin', label: 'Administrator' },
233
- { value: 'user', label: 'Regular User' }
234
- ]}
235
- required
236
- />
237
-
238
- <FormCheckbox
239
- name="active"
240
- label="Active User"
241
- />
242
-
243
- <FormButton type="submit" variant="contained">
244
- Save User
245
- </FormButton>
246
- </PanelForm>
38
+ ```sh
39
+ npx shadcn add button card input label progress alert separator
247
40
  ```
248
41
 
249
- ### Button Components
42
+ This package was authored against the shadcn `new-york` style with
43
+ `baseColor: neutral`. See `components.json` for the exact config.
250
44
 
251
- Enhanced button components with loading states and Material-UI integration.
45
+ ### 2. Add Tailwind v4 theme tokens
252
46
 
253
- ```typescript
254
- import { PanelButton, IconButton, FloatingActionButton } from '@owlmeans/web-panel'
47
+ The components rely on the following CSS variables (defined inside
48
+ `@theme` in your app's globals.css):
255
49
 
256
- <PanelButton
257
- variant="contained"
258
- color="primary"
259
- startIcon={<SaveIcon />}
260
- loading={isSaving}
261
- onClick={handleSave}
262
- >
263
- Save Changes
264
- </PanelButton>
50
+ - `--color-background`, `--color-foreground`
51
+ - `--color-card`, `--color-card-foreground`
52
+ - `--color-primary`, `--color-primary-foreground`
53
+ - `--color-secondary`, `--color-secondary-foreground`
54
+ - `--color-muted`, `--color-muted-foreground`
55
+ - `--color-accent`, `--color-accent-foreground`
56
+ - `--color-destructive`
57
+ - `--color-success` (custom; only needed if you use `<Status ok />`)
58
+ - `--color-border`, `--color-input`, `--color-ring`
59
+ - `--radius`
60
+ - `--animate-progress-indeterminate` + the `@keyframes progress-indeterminate`
61
+ rule for the `Progress` component's indeterminate mode
265
62
 
266
- <IconButton
267
- color="secondary"
268
- onClick={handleEdit}
269
- tooltip="Edit Item"
270
- >
271
- <EditIcon />
272
- </IconButton>
63
+ A working set is shipped at `src/@/globals.css` (for dev/test only).
273
64
 
274
- <FloatingActionButton
275
- color="primary"
276
- onClick={handleAdd}
277
- position="bottom-right"
278
- >
279
- <AddIcon />
280
- </FloatingActionButton>
281
- ```
282
-
283
- ### File Upload Components
284
-
285
- Comprehensive file upload system with progress tracking and validation.
286
-
287
- ```typescript
288
- import {
289
- FileUploader,
290
- ImageUploader,
291
- MultiFileUploader,
292
- DropZone
293
- } from '@owlmeans/web-panel'
65
+ ### 3. Bundler alias
294
66
 
295
- <FileUploader
296
- accept=".pdf,.doc,.docx"
297
- maxSize={10 * 1024 * 1024} // 10MB
298
- onUpload={handleFileUpload}
299
- onProgress={handleProgress}
300
- onError={handleError}
301
- />
302
-
303
- <ImageUploader
304
- accept="image/*"
305
- maxSize={5 * 1024 * 1024} // 5MB
306
- cropAspectRatio={16/9}
307
- onUpload={handleImageUpload}
308
- preview={true}
309
- />
310
-
311
- <MultiFileUploader
312
- maxFiles={5}
313
- accept="*/*"
314
- onUploadAll={handleMultipleUpload}
315
- showProgress={true}
316
- />
317
-
318
- <DropZone
319
- onDrop={handleFileDrop}
320
- accept=".zip,.tar.gz"
321
- multiple={false}
322
- >
323
- Drop files here or click to browse
324
- </DropZone>
67
+ ```ts
68
+ // vite.config.ts
69
+ resolve: { alias: { '@': fileURLToPath(new URL('./src/@', import.meta.url)) } }
325
70
  ```
326
71
 
327
- ### Text and Display Components
72
+ ## Breaking changes vs `@owlmeans/mui-panel`
328
73
 
329
- Typography and content display components.
330
-
331
- ```typescript
332
- import {
333
- PanelText,
334
- PanelLink,
335
- StatusIndicator,
336
- InfoBlock
337
- } from '@owlmeans/web-panel'
74
+ This package keeps the same public name and re-export surface as the
75
+ previous MUI implementation, but the following props/types have changed:
338
76
 
339
- <PanelText variant="h4" color="primary" gutterBottom>
340
- Panel Title
341
- </PanelText>
342
-
343
- <PanelLink href="/admin/users" external={false}>
344
- Manage Users
345
- </PanelLink>
346
-
347
- <StatusIndicator
348
- status="success"
349
- message="Operation completed successfully"
350
- />
351
-
352
- <InfoBlock
353
- title="Important Notice"
354
- severity="warning"
355
- actions={[
356
- { label: 'Dismiss', onClick: handleDismiss },
357
- { label: 'Learn More', onClick: openDetails }
358
- ]}
359
- >
360
- This action cannot be undone.
361
- </InfoBlock>
362
- ```
77
+ - **`styles?: SxProps` is removed.** Use the new `className?: string` and
78
+ `style?: React.CSSProperties` props instead. Affected: `BlockProps`,
79
+ `TextProps`, `LinkProps`, `WebFormProps`, `LayoutProps`.
80
+ - **`variant` on `Text` / `Link` is now `TextVariant`**, a string-literal
81
+ union: `'h1' | 'h2' | 'h3' | 'h4' | 'p' | 'lead' | 'large' | 'small' |
82
+ 'muted' | 'blockquote'`. The previous MUI `TypographyOwnProps['variant']`
83
+ union no longer applies.
84
+ - **`PanelAppProps.theme?: Theme` is replaced with
85
+ `PanelAppProps.rootClassName?: string`.** Apply your theme via Tailwind
86
+ classes / CSS variables instead of MUI's `Theme` object.
87
+ - **`render(context, theme?, opts?)` signature → `render(context, opts?)`**
88
+ where `opts` includes the new `rootClassName?: string`.
89
+ - **`scalingToStyles()`** now returns a class-name string (composable with
90
+ `cn()`), not an `SxProps` object. The `theme?: Theme` parameter is
91
+ removed (Tailwind handles breakpoints declaratively).
92
+ - **`useBreakPoint` / `useMapBreakpoint`** now use Tailwind's static
93
+ default breakpoints (`xs/sm/md/lg/xl`) instead of MUI's `Theme.breakpoints`.
94
+ Customise via Tailwind config in the consuming app.
95
+ - **Component prop API for buttons** stays compatible (`'small' | 'medium'
96
+ | 'large'`, `variant` strings `contained`/`outlined`/`text`/shadcn names).
97
+ Internally they map to shadcn variants.
363
98
 
364
- ### Authentication Components
99
+ Consumers that need the previous MUI behaviour should swap to
100
+ [`@owlmeans/mui-panel`](../mui-panel) — same exports, MUI-rendered.
365
101
 
366
- Built-in authentication UI components and flows.
102
+ ## Public exports
367
103
 
368
- ```typescript
369
- import {
370
- LoginForm,
371
- LogoutButton,
372
- UserProfile,
373
- AuthGuard,
374
- PermissionGuard
375
- } from '@owlmeans/web-panel/auth'
376
-
377
- <AuthGuard fallback={<LoginForm />}>
378
- <PanelApp context={context}>
379
- <PermissionGuard permissions={['admin']}>
380
- <AdminPanel />
381
- </PermissionGuard>
382
- </PanelApp>
383
- </AuthGuard>
384
-
385
- <LoginForm
386
- onSuccess={handleLoginSuccess}
387
- onError={handleLoginError}
388
- showRegister={true}
389
- showForgotPassword={true}
390
- />
391
-
392
- <UserProfile
393
- showAvatar={true}
394
- showMenu={true}
395
- menuActions={[
396
- { label: 'Settings', onClick: openSettings },
397
- { label: 'Logout', onClick: handleLogout }
398
- ]}
399
- />
400
- ```
401
-
402
- ## Usage Examples
403
-
404
- ### Complete Panel Application
405
-
406
- ```typescript
407
- import React from 'react'
408
- import { render } from '@owlmeans/web-panel'
409
- import { makeWebContext } from '@owlmeans/web-client'
410
- import { createTheme } from '@mui/material/styles'
411
- import {
412
- PanelApp,
413
- PanelLayout,
414
- PanelHeader,
415
- PanelSidebar,
416
- PanelContent,
417
- AuthGuard
418
- } from '@owlmeans/web-panel'
419
-
420
- const config = {
421
- service: 'admin-panel',
422
- type: AppType.Frontend,
423
- layer: Layer.Service
424
- }
425
-
426
- const theme = createTheme({
427
- palette: {
428
- mode: 'light',
429
- primary: {
430
- main: '#1976d2',
431
- contrastText: '#ffffff'
432
- },
433
- secondary: {
434
- main: '#dc004e'
435
- },
436
- background: {
437
- default: '#f5f5f5',
438
- paper: '#ffffff'
439
- }
440
- },
441
- typography: {
442
- fontFamily: 'Roboto, Arial, sans-serif',
443
- h1: {
444
- fontSize: '2.5rem',
445
- fontWeight: 500
446
- }
447
- },
448
- shape: {
449
- borderRadius: 8
450
- }
451
- })
452
-
453
- const AdminPanelApp: React.FC = () => {
454
- const context = makeWebContext(config)
455
-
456
- return (
457
- <PanelApp context={context} theme={theme}>
458
- <AuthGuard>
459
- <PanelLayout>
460
- <PanelHeader title="Administration Panel">
461
- <UserProfile />
462
- </PanelHeader>
463
-
464
- <PanelSidebar>
465
- <AdminNavigation />
466
- </PanelSidebar>
467
-
468
- <PanelContent>
469
- <AdminRouter />
470
- </PanelContent>
471
- </PanelLayout>
472
- </AuthGuard>
473
- </PanelApp>
474
- )
475
- }
476
-
477
- // Render the application
478
- render(makeWebContext(config), theme)
479
- ```
480
-
481
- ### User Management Interface
482
-
483
- ```typescript
484
- import React, { useState, useEffect } from 'react'
104
+ ```ts
485
105
  import {
486
- PanelForm,
487
- FormField,
488
- FormSelect,
489
- FormButton,
490
- PanelButton,
491
- StatusIndicator,
492
- InfoBlock
106
+ PanelApp, Layout, Form, TextInput, SubmitButton, Button, ButtonSelector,
107
+ Block, Text, Link, Status, ImageUploader,
108
+ scalingToStyles, useBreakPoint, useMapBreakpoint,
109
+ render,
493
110
  } from '@owlmeans/web-panel'
494
- import { Grid, Card, CardContent, Typography } from '@mui/material'
495
-
496
- interface User {
497
- id: string
498
- name: string
499
- email: string
500
- role: string
501
- active: boolean
502
- createdAt: Date
503
- }
504
111
 
505
- const UserManagement: React.FC = () => {
506
- const [users, setUsers] = useState<User[]>([])
507
- const [loading, setLoading] = useState(true)
508
- const [editing, setEditing] = useState<User | null>(null)
509
- const [error, setError] = useState<string | null>(null)
510
-
511
- useEffect(() => {
512
- loadUsers()
513
- }, [])
514
-
515
- const loadUsers = async () => {
516
- try {
517
- setLoading(true)
518
- const response = await fetch('/api/users')
519
- const userData = await response.json()
520
- setUsers(userData)
521
- } catch (err) {
522
- setError('Failed to load users')
523
- } finally {
524
- setLoading(false)
525
- }
526
- }
527
-
528
- const handleUserSave = async (userData: Partial<User>) => {
529
- try {
530
- const url = editing ? `/api/users/${editing.id}` : '/api/users'
531
- const method = editing ? 'PUT' : 'POST'
532
-
533
- const response = await fetch(url, {
534
- method,
535
- headers: { 'Content-Type': 'application/json' },
536
- body: JSON.stringify(userData)
537
- })
538
-
539
- if (response.ok) {
540
- await loadUsers()
541
- setEditing(null)
542
- } else {
543
- setError('Failed to save user')
544
- }
545
- } catch (err) {
546
- setError('Network error')
547
- }
548
- }
549
-
550
- const handleUserDelete = async (userId: string) => {
551
- if (!confirm('Are you sure you want to delete this user?')) return
552
-
553
- try {
554
- const response = await fetch(`/api/users/${userId}`, { method: 'DELETE' })
555
- if (response.ok) {
556
- await loadUsers()
557
- } else {
558
- setError('Failed to delete user')
559
- }
560
- } catch (err) {
561
- setError('Network error')
562
- }
563
- }
564
-
565
- const userSchema = {
566
- type: 'object',
567
- properties: {
568
- name: { type: 'string', minLength: 2, maxLength: 100 },
569
- email: { type: 'string', format: 'email' },
570
- role: { type: 'string', enum: ['admin', 'user', 'moderator'] },
571
- active: { type: 'boolean' }
572
- },
573
- required: ['name', 'email', 'role']
574
- }
575
-
576
- if (loading) {
577
- return <StatusIndicator status="loading" message="Loading users..." />
578
- }
579
-
580
- return (
581
- <Grid container spacing={3}>
582
- <Grid item xs={12}>
583
- <Typography variant="h4" gutterBottom>
584
- User Management
585
- </Typography>
586
-
587
- {error && (
588
- <InfoBlock severity="error" onClose={() => setError(null)}>
589
- {error}
590
- </InfoBlock>
591
- )}
592
- </Grid>
593
-
594
- <Grid item xs={12} md={8}>
595
- <Card>
596
- <CardContent>
597
- <Typography variant="h6" gutterBottom>
598
- Users
599
- </Typography>
600
-
601
- <PanelButton
602
- variant="contained"
603
- color="primary"
604
- onClick={() => setEditing({} as User)}
605
- sx={{ mb: 2 }}
606
- >
607
- Add New User
608
- </PanelButton>
609
-
610
- {users.map(user => (
611
- <Card key={user.id} variant="outlined" sx={{ mb: 1 }}>
612
- <CardContent>
613
- <Grid container alignItems="center" spacing={2}>
614
- <Grid item xs>
615
- <Typography variant="subtitle1">{user.name}</Typography>
616
- <Typography variant="body2" color="textSecondary">
617
- {user.email} • {user.role}
618
- </Typography>
619
- </Grid>
620
- <Grid item>
621
- <StatusIndicator
622
- status={user.active ? 'success' : 'warning'}
623
- message={user.active ? 'Active' : 'Inactive'}
624
- />
625
- </Grid>
626
- <Grid item>
627
- <PanelButton
628
- size="small"
629
- onClick={() => setEditing(user)}
630
- >
631
- Edit
632
- </PanelButton>
633
- <PanelButton
634
- size="small"
635
- color="error"
636
- onClick={() => handleUserDelete(user.id)}
637
- sx={{ ml: 1 }}
638
- >
639
- Delete
640
- </PanelButton>
641
- </Grid>
642
- </Grid>
643
- </CardContent>
644
- </Card>
645
- ))}
646
- </CardContent>
647
- </Card>
648
- </Grid>
649
-
650
- <Grid item xs={12} md={4}>
651
- {editing && (
652
- <Card>
653
- <CardContent>
654
- <Typography variant="h6" gutterBottom>
655
- {editing.id ? 'Edit User' : 'Add User'}
656
- </Typography>
657
-
658
- <PanelForm
659
- schema={userSchema}
660
- defaultValues={editing}
661
- onSubmit={handleUserSave}
662
- >
663
- <FormField
664
- name="name"
665
- label="Full Name"
666
- required
667
- fullWidth
668
- margin="normal"
669
- />
670
-
671
- <FormField
672
- name="email"
673
- label="Email Address"
674
- type="email"
675
- required
676
- fullWidth
677
- margin="normal"
678
- />
679
-
680
- <FormSelect
681
- name="role"
682
- label="Role"
683
- required
684
- fullWidth
685
- margin="normal"
686
- options={[
687
- { value: 'user', label: 'Regular User' },
688
- { value: 'moderator', label: 'Moderator' },
689
- { value: 'admin', label: 'Administrator' }
690
- ]}
691
- />
692
-
693
- <FormField
694
- name="active"
695
- label="Active User"
696
- type="checkbox"
697
- margin="normal"
698
- />
699
-
700
- <Grid container spacing={2} sx={{ mt: 2 }}>
701
- <Grid item>
702
- <FormButton
703
- type="submit"
704
- variant="contained"
705
- color="primary"
706
- >
707
- {editing.id ? 'Update' : 'Create'}
708
- </FormButton>
709
- </Grid>
710
- <Grid item>
711
- <PanelButton
712
- variant="outlined"
713
- onClick={() => setEditing(null)}
714
- >
715
- Cancel
716
- </PanelButton>
717
- </Grid>
718
- </Grid>
719
- </PanelForm>
720
- </CardContent>
721
- </Card>
722
- )}
723
- </Grid>
724
- </Grid>
725
- )
726
- }
727
-
728
- export default UserManagement
729
- ```
730
-
731
- ### File Upload Interface
732
-
733
- ```typescript
734
- import React, { useState } from 'react'
735
- import {
736
- FileUploader,
737
- ImageUploader,
738
- MultiFileUploader,
739
- StatusIndicator,
740
- PanelButton
741
- } from '@owlmeans/web-panel'
742
- import { Grid, Card, CardContent, Typography, LinearProgress } from '@mui/material'
743
-
744
- const FileManagement: React.FC = () => {
745
- const [uploadProgress, setUploadProgress] = useState<Record<string, number>>({})
746
- const [uploadedFiles, setUploadedFiles] = useState<string[]>([])
747
-
748
- const handleFileUpload = async (file: File, progressCallback: (progress: number) => void) => {
749
- const formData = new FormData()
750
- formData.append('file', file)
751
-
752
- try {
753
- const xhr = new XMLHttpRequest()
754
-
755
- xhr.upload.addEventListener('progress', (e) => {
756
- if (e.lengthComputable) {
757
- const progress = (e.loaded / e.total) * 100
758
- progressCallback(progress)
759
- setUploadProgress(prev => ({ ...prev, [file.name]: progress }))
760
- }
761
- })
762
-
763
- xhr.addEventListener('load', () => {
764
- if (xhr.status === 200) {
765
- setUploadedFiles(prev => [...prev, file.name])
766
- setUploadProgress(prev => {
767
- const updated = { ...prev }
768
- delete updated[file.name]
769
- return updated
770
- })
771
- }
772
- })
773
-
774
- xhr.open('POST', '/api/upload')
775
- xhr.send(formData)
776
- } catch (error) {
777
- console.error('Upload failed:', error)
778
- }
779
- }
780
-
781
- const handleImageUpload = async (file: File, cropData?: any) => {
782
- console.log('Uploading image:', file.name, 'Crop data:', cropData)
783
- // Implement image upload with crop data
784
- }
785
-
786
- const handleMultipleUpload = async (files: File[]) => {
787
- for (const file of files) {
788
- await handleFileUpload(file, () => {})
789
- }
790
- }
791
-
792
- return (
793
- <Grid container spacing={3}>
794
- <Grid item xs={12}>
795
- <Typography variant="h4" gutterBottom>
796
- File Management
797
- </Typography>
798
- </Grid>
799
-
800
- <Grid item xs={12} md={6}>
801
- <Card>
802
- <CardContent>
803
- <Typography variant="h6" gutterBottom>
804
- Document Upload
805
- </Typography>
806
-
807
- <FileUploader
808
- accept=".pdf,.doc,.docx,.txt"
809
- maxSize={10 * 1024 * 1024} // 10MB
810
- onUpload={handleFileUpload}
811
- multiple={false}
812
- />
813
- </CardContent>
814
- </Card>
815
- </Grid>
816
-
817
- <Grid item xs={12} md={6}>
818
- <Card>
819
- <CardContent>
820
- <Typography variant="h6" gutterBottom>
821
- Image Upload
822
- </Typography>
823
-
824
- <ImageUploader
825
- accept="image/*"
826
- maxSize={5 * 1024 * 1024} // 5MB
827
- cropAspectRatio={16/9}
828
- onUpload={handleImageUpload}
829
- preview={true}
830
- cropperOptions={{
831
- guides: false,
832
- center: false,
833
- highlight: false,
834
- background: false,
835
- autoCropArea: 1,
836
- checkOrientation: false
837
- }}
838
- />
839
- </CardContent>
840
- </Card>
841
- </Grid>
842
-
843
- <Grid item xs={12}>
844
- <Card>
845
- <CardContent>
846
- <Typography variant="h6" gutterBottom>
847
- Multiple File Upload
848
- </Typography>
849
-
850
- <MultiFileUploader
851
- maxFiles={5}
852
- accept="*/*"
853
- onUploadAll={handleMultipleUpload}
854
- showProgress={true}
855
- />
856
-
857
- {Object.entries(uploadProgress).map(([fileName, progress]) => (
858
- <div key={fileName}>
859
- <Typography variant="body2" gutterBottom>
860
- {fileName}
861
- </Typography>
862
- <LinearProgress
863
- variant="determinate"
864
- value={progress}
865
- sx={{ mb: 1 }}
866
- />
867
- </div>
868
- ))}
869
- </CardContent>
870
- </Card>
871
- </Grid>
872
-
873
- {uploadedFiles.length > 0 && (
874
- <Grid item xs={12}>
875
- <Card>
876
- <CardContent>
877
- <Typography variant="h6" gutterBottom>
878
- Uploaded Files
879
- </Typography>
880
-
881
- {uploadedFiles.map(fileName => (
882
- <StatusIndicator
883
- key={fileName}
884
- status="success"
885
- message={`${fileName} uploaded successfully`}
886
- />
887
- ))}
888
- </CardContent>
889
- </Card>
890
- </Grid>
891
- )}
892
- </Grid>
893
- )
894
- }
895
-
896
- export default FileManagement
897
- ```
898
-
899
- ## Authentication Integration
900
-
901
- ### Protected Routes and Components
902
-
903
- ```typescript
904
- import { AuthGuard, PermissionGuard } from '@owlmeans/web-panel/auth'
905
- import { Navigate } from 'react-router-dom'
906
-
907
- // Protect entire application
908
- <AuthGuard fallback={<Navigate to="/login" />}>
909
- <PanelApp context={context}>
910
- <Router>
911
- <Routes>
912
- <Route path="/admin" element={
913
- <PermissionGuard permissions={['admin']}>
914
- <AdminPanel />
915
- </PermissionGuard>
916
- } />
917
- <Route path="/users" element={
918
- <PermissionGuard permissions={['user-management']}>
919
- <UserManagement />
920
- </PermissionGuard>
921
- } />
922
- </Routes>
923
- </Router>
924
- </PanelApp>
925
- </AuthGuard>
926
-
927
- // Conditional rendering based on permissions
928
- const AdminActions: React.FC = () => (
929
- <PermissionGuard permissions={['admin', 'moderator']}>
930
- {(hasPermission) => (
931
- <PanelButton
932
- disabled={!hasPermission}
933
- onClick={handleAdminAction}
934
- >
935
- Admin Action
936
- </PanelButton>
937
- )}
938
- </PermissionGuard>
939
- )
940
- ```
941
-
942
- ## Theme Customization
943
-
944
- ### Custom Material-UI Theme
945
-
946
- ```typescript
947
- import { createTheme, ThemeProvider } from '@mui/material/styles'
948
-
949
- const customTheme = createTheme({
950
- palette: {
951
- mode: 'light',
952
- primary: {
953
- main: '#1976d2',
954
- light: '#42a5f5',
955
- dark: '#1565c0',
956
- contrastText: '#ffffff'
957
- },
958
- secondary: {
959
- main: '#dc004e',
960
- light: '#f06292',
961
- dark: '#c2185b',
962
- contrastText: '#ffffff'
963
- },
964
- background: {
965
- default: '#f5f5f5',
966
- paper: '#ffffff'
967
- },
968
- text: {
969
- primary: '#212121',
970
- secondary: '#757575'
971
- }
972
- },
973
- typography: {
974
- fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
975
- h1: {
976
- fontSize: '2.5rem',
977
- fontWeight: 500,
978
- lineHeight: 1.2
979
- },
980
- h2: {
981
- fontSize: '2rem',
982
- fontWeight: 500,
983
- lineHeight: 1.3
984
- },
985
- body1: {
986
- fontSize: '1rem',
987
- lineHeight: 1.5
988
- }
989
- },
990
- shape: {
991
- borderRadius: 8
992
- },
993
- spacing: 8,
994
- components: {
995
- MuiCard: {
996
- styleOverrides: {
997
- root: {
998
- boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
999
- '&:hover': {
1000
- boxShadow: '0 4px 16px rgba(0,0,0,0.15)'
1001
- }
1002
- }
1003
- }
1004
- },
1005
- MuiButton: {
1006
- styleOverrides: {
1007
- root: {
1008
- borderRadius: 8,
1009
- textTransform: 'none',
1010
- fontWeight: 500
1011
- }
1012
- }
1013
- }
1014
- }
1015
- })
1016
-
1017
- // Apply theme to panel app
1018
- <PanelApp context={context} theme={customTheme}>
1019
- <AdminInterface />
1020
- </PanelApp>
1021
- ```
1022
-
1023
- ## Internationalization
1024
-
1025
- ### Multi-language Support
1026
-
1027
- ```typescript
1028
- import { addI18nApp } from '@owlmeans/i18n'
1029
-
1030
- // Add translations for panel
1031
- addI18nApp('en', 'panel', {
1032
- 'panel.title': 'Administration Panel',
1033
- 'users.title': 'User Management',
1034
- 'users.add': 'Add New User',
1035
- 'users.edit': 'Edit User',
1036
- 'users.delete': 'Delete User',
1037
- 'form.save': 'Save',
1038
- 'form.cancel': 'Cancel',
1039
- 'status.loading': 'Loading...',
1040
- 'status.error': 'An error occurred',
1041
- 'status.success': 'Operation completed successfully'
1042
- })
1043
-
1044
- addI18nApp('es', 'panel', {
1045
- 'panel.title': 'Panel de Administración',
1046
- 'users.title': 'Gestión de Usuarios',
1047
- 'users.add': 'Agregar Nuevo Usuario',
1048
- 'users.edit': 'Editar Usuario',
1049
- 'users.delete': 'Eliminar Usuario',
1050
- 'form.save': 'Guardar',
1051
- 'form.cancel': 'Cancelar',
1052
- 'status.loading': 'Cargando...',
1053
- 'status.error': 'Ocurrió un error',
1054
- 'status.success': 'Operación completada exitosamente'
1055
- })
1056
-
1057
- // Use translations in components
1058
- import { useTranslation } from '@owlmeans/client-i18n'
1059
-
1060
- const UserPanel: React.FC = () => {
1061
- const { t } = useTranslation()
1062
-
1063
- return (
1064
- <Typography variant="h4">
1065
- {t('users.title')}
1066
- </Typography>
1067
- )
1068
- }
1069
- ```
1070
-
1071
- ## Performance Optimization
1072
-
1073
- ### Code Splitting and Lazy Loading
1074
-
1075
- ```typescript
1076
- import React, { lazy, Suspense } from 'react'
1077
- import { StatusIndicator } from '@owlmeans/web-panel'
1078
-
1079
- // Lazy load panel sections
1080
- const UserManagement = lazy(() => import('./UserManagement'))
1081
- const SystemSettings = lazy(() => import('./SystemSettings'))
1082
- const Reports = lazy(() => import('./Reports'))
1083
-
1084
- const AdminRouter: React.FC = () => (
1085
- <Routes>
1086
- <Route path="/users" element={
1087
- <Suspense fallback={<StatusIndicator status="loading" message="Loading users..." />}>
1088
- <UserManagement />
1089
- </Suspense>
1090
- } />
1091
- <Route path="/settings" element={
1092
- <Suspense fallback={<StatusIndicator status="loading" message="Loading settings..." />}>
1093
- <SystemSettings />
1094
- </Suspense>
1095
- } />
1096
- <Route path="/reports" element={
1097
- <Suspense fallback={<StatusIndicator status="loading" message="Loading reports..." />}>
1098
- <Reports />
1099
- </Suspense>
1100
- } />
1101
- </Routes>
1102
- )
1103
- ```
1104
-
1105
- ### Memoization and Optimization
1106
-
1107
- ```typescript
1108
- import React, { memo, useMemo, useCallback } from 'react'
1109
-
1110
- const OptimizedUserList = memo<{ users: User[]; onUserSelect: (user: User) => void }>(({
1111
- users,
1112
- onUserSelect
1113
- }) => {
1114
- const sortedUsers = useMemo(() =>
1115
- users.sort((a, b) => a.name.localeCompare(b.name)),
1116
- [users]
1117
- )
1118
-
1119
- const handleUserClick = useCallback((user: User) => {
1120
- onUserSelect(user)
1121
- }, [onUserSelect])
1122
-
1123
- return (
1124
- <div>
1125
- {sortedUsers.map(user => (
1126
- <UserCard
1127
- key={user.id}
1128
- user={user}
1129
- onClick={handleUserClick}
1130
- />
1131
- ))}
1132
- </div>
1133
- )
1134
- })
1135
- ```
1136
-
1137
- ## Best Practices
1138
-
1139
- 1. **Component Structure**: Use consistent component hierarchy and naming conventions
1140
- 2. **Theme Management**: Leverage Material-UI theme system for consistent styling
1141
- 3. **Form Validation**: Use AJV schemas for robust form validation
1142
- 4. **Error Handling**: Provide comprehensive error feedback and recovery options
1143
- 5. **Performance**: Implement code splitting and memoization for large applications
1144
- 6. **Accessibility**: Ensure all components meet WCAG accessibility standards
1145
- 7. **Internationalization**: Always use i18n for user-facing text
1146
- 8. **Testing**: Write comprehensive tests for form validation and user interactions
1147
-
1148
- ## Authentication Modules
1149
-
1150
- The package provides authentication-specific modules and components:
1151
-
1152
- ```typescript
112
+ import { setupExternalAuthentication } from '@owlmeans/web-panel/auth'
1153
113
  import { modules } from '@owlmeans/web-panel/auth/modules'
1154
- import { AuthProvider, LoginForm } from '@owlmeans/web-panel/auth'
1155
-
1156
- // Register authentication modules
1157
- context.registerModules(modules)
1158
-
1159
- // Use authentication components
1160
- <AuthProvider context={context}>
1161
- <LoginForm
1162
- onSuccess={handleLogin}
1163
- features={{
1164
- showRegister: true,
1165
- showForgotPassword: true,
1166
- showRememberMe: true
1167
- }}
1168
- />
1169
- </AuthProvider>
1170
114
  ```
1171
115
 
1172
- ## Related Packages
1173
-
1174
- - **@owlmeans/client-panel**: Base panel functionality and context
1175
- - **@owlmeans/native-panel**: React Native panel implementation
1176
- - **@owlmeans/web-client**: Web client framework and rendering
1177
- - **@owlmeans/client-auth**: Authentication components and services
1178
- - **@owlmeans/client-flow**: Flow management integration
1179
- - **@owlmeans/web-flow**: Web-specific flow implementations
1180
-
1181
- ## TypeScript Support
1182
-
1183
- This package is written in TypeScript and provides full type safety:
1184
-
1185
- ```typescript
1186
- import type {
1187
- PanelAppProps,
1188
- FormFieldProps,
1189
- FileUploaderProps,
1190
- AuthGuardProps
1191
- } from '@owlmeans/web-panel'
116
+ ## Related packages
1192
117
 
1193
- const MyPanel: FC<PanelAppProps> = (props) => { /* ... */ }
1194
- const MyForm: FC<{ onSubmit: (data: FormData) => void }> = ({ onSubmit }) => { /* ... */ }
1195
- ```
118
+ - [`@owlmeans/mui-panel`](../mui-panel) Material UI implementation of the
119
+ same surface.
120
+ - [`@owlmeans/web-oidc-rp`](../web-oidc-rp) — companion OIDC/OAuth UI for
121
+ this package; uses the same Tailwind theme.
122
+ - [`@owlmeans/client-panel`](../client-panel) — framework-agnostic headless
123
+ form/layout logic this package wraps.