@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.
- package/README.md +88 -1160
- package/build/@/components/ui/alert.d.ts +10 -0
- package/build/@/components/ui/alert.d.ts.map +1 -0
- package/build/@/components/ui/alert.js +26 -0
- package/build/@/components/ui/alert.js.map +1 -0
- package/build/@/components/ui/button.d.ts +11 -0
- package/build/@/components/ui/button.d.ts.map +1 -0
- package/build/@/components/ui/button.js +32 -0
- package/build/@/components/ui/button.js.map +1 -0
- package/build/@/components/ui/card.d.ts +10 -0
- package/build/@/components/ui/card.d.ts.map +1 -0
- package/build/@/components/ui/card.js +25 -0
- package/build/@/components/ui/card.js.map +1 -0
- package/build/@/components/ui/input.d.ts +4 -0
- package/build/@/components/ui/input.d.ts.map +1 -0
- package/build/@/components/ui/input.js +7 -0
- package/build/@/components/ui/input.js.map +1 -0
- package/build/@/components/ui/label.d.ts +5 -0
- package/build/@/components/ui/label.d.ts.map +1 -0
- package/build/@/components/ui/label.js +8 -0
- package/build/@/components/ui/label.js.map +1 -0
- package/build/@/components/ui/progress.d.ts +5 -0
- package/build/@/components/ui/progress.d.ts.map +1 -0
- package/build/@/components/ui/progress.js +11 -0
- package/build/@/components/ui/progress.js.map +1 -0
- package/build/@/components/ui/separator.d.ts +5 -0
- package/build/@/components/ui/separator.d.ts.map +1 -0
- package/build/@/components/ui/separator.js +8 -0
- package/build/@/components/ui/separator.js.map +1 -0
- package/build/@/lib/utils.d.ts +3 -0
- package/build/@/lib/utils.d.ts.map +1 -0
- package/build/@/lib/utils.js +6 -0
- package/build/@/lib/utils.js.map +1 -0
- package/build/auth/exports.d.ts +4 -4
- package/build/auth/exports.d.ts.map +1 -1
- package/build/auth/exports.js +2 -2
- package/build/auth/exports.js.map +1 -1
- package/build/auth/modules.d.ts +1 -1
- package/build/auth/modules.d.ts.map +1 -1
- package/build/auth/plugins/basic-ed25519.js +2 -2
- package/build/auth/plugins/basic-ed25519.js.map +1 -1
- package/build/auth/plugins/re-captcha.d.ts.map +1 -1
- package/build/auth/plugins/re-captcha.js +4 -12
- package/build/auth/plugins/re-captcha.js.map +1 -1
- package/build/auth/plugins/tunnel-consumer.d.ts.map +1 -1
- package/build/auth/plugins/tunnel-consumer.js +28 -16
- package/build/auth/plugins/tunnel-consumer.js.map +1 -1
- package/build/components/block.d.ts.map +1 -1
- package/build/components/block.js +5 -8
- package/build/components/block.js.map +1 -1
- package/build/components/button/selector.d.ts.map +1 -1
- package/build/components/button/selector.js +1 -2
- package/build/components/button/selector.js.map +1 -1
- package/build/components/form/button/component.d.ts +1 -1
- package/build/components/form/button/component.d.ts.map +1 -1
- package/build/components/form/button/component.js +39 -8
- package/build/components/form/button/component.js.map +1 -1
- package/build/components/form/component.d.ts.map +1 -1
- package/build/components/form/component.js +10 -13
- package/build/components/form/component.js.map +1 -1
- package/build/components/form/text/component.d.ts +1 -1
- package/build/components/form/text/component.d.ts.map +1 -1
- package/build/components/form/text/component.js +23 -24
- package/build/components/form/text/component.js.map +1 -1
- package/build/components/form/types.d.ts +3 -2
- package/build/components/form/types.d.ts.map +1 -1
- package/build/components/helper.d.ts +11 -2
- package/build/components/helper.d.ts.map +1 -1
- package/build/components/helper.js +50 -40
- package/build/components/helper.js.map +1 -1
- package/build/components/layout/component.d.ts.map +1 -1
- package/build/components/layout/component.js +2 -3
- package/build/components/layout/component.js.map +1 -1
- package/build/components/layout/types.d.ts +3 -1
- package/build/components/layout/types.d.ts.map +1 -1
- package/build/components/link.d.ts.map +1 -1
- package/build/components/link.js +6 -4
- package/build/components/link.js.map +1 -1
- package/build/components/panel-app/component.d.ts.map +1 -1
- package/build/components/panel-app/component.js +4 -7
- package/build/components/panel-app/component.js.map +1 -1
- package/build/components/panel-app/types.d.ts +6 -2
- package/build/components/panel-app/types.d.ts.map +1 -1
- package/build/components/status.d.ts.map +1 -1
- package/build/components/status.js +18 -7
- package/build/components/status.js.map +1 -1
- package/build/components/text.d.ts.map +1 -1
- package/build/components/text.js +26 -3
- package/build/components/text.js.map +1 -1
- package/build/components/types.d.ts +18 -10
- package/build/components/types.d.ts.map +1 -1
- package/build/components/uploader/image.d.ts.map +1 -1
- package/build/components/uploader/image.js +11 -19
- package/build/components/uploader/image.js.map +1 -1
- package/build/exports.d.ts +3 -3
- package/build/exports.d.ts.map +1 -1
- package/build/exports.js +2 -2
- package/build/exports.js.map +1 -1
- package/build/main.d.ts +4 -2
- package/build/main.d.ts.map +1 -1
- package/build/main.js +4 -4
- package/build/main.js.map +1 -1
- package/build/modules.d.ts +1 -1
- package/build/modules.d.ts.map +1 -1
- package/components.json +21 -0
- package/package.json +53 -34
- package/src/@/components/ui/alert.tsx +70 -0
- package/src/@/components/ui/button.tsx +60 -0
- package/src/@/components/ui/card.tsx +93 -0
- package/src/@/components/ui/input.tsx +22 -0
- package/src/@/components/ui/label.tsx +23 -0
- package/src/@/components/ui/progress.tsx +44 -0
- package/src/@/components/ui/separator.tsx +27 -0
- package/src/@/globals.css +64 -0
- package/src/@/lib/utils.ts +6 -0
- package/src/auth/exports.ts +4 -4
- package/src/auth/plugins/basic-ed25519.tsx +2 -2
- package/src/auth/plugins/re-captcha.tsx +14 -22
- package/src/auth/plugins/tunnel-consumer.tsx +32 -24
- package/src/components/block.tsx +10 -14
- package/src/components/button/selector.tsx +9 -9
- package/src/components/form/button/component.tsx +54 -14
- package/src/components/form/component.tsx +23 -24
- package/src/components/form/text/component.tsx +39 -30
- package/src/components/form/types.ts +4 -3
- package/src/components/helper.ts +56 -42
- package/src/components/layout/component.tsx +2 -3
- package/src/components/layout/types.ts +3 -1
- package/src/components/link.tsx +17 -7
- package/src/components/panel-app/component.tsx +5 -9
- package/src/components/panel-app/types.ts +6 -2
- package/src/components/status.tsx +20 -9
- package/src/components/text.tsx +28 -9
- package/src/components/types.ts +22 -10
- package/src/components/uploader/image.tsx +23 -23
- package/src/exports.ts +3 -3
- package/src/main.tsx +8 -5
- package/tests/smoke.spec.ts +24 -0
- package/tsconfig.json +9 -11
package/README.md
CHANGED
|
@@ -1,1195 +1,123 @@
|
|
|
1
1
|
# @owlmeans/web-panel
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
##
|
|
7
|
+
## When to use this package
|
|
6
8
|
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
+
## Consumer setup — the `@` contract
|
|
178
27
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
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
|
-
|
|
199
|
-
|
|
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
|
-
|
|
210
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
+
### 2. Add Tailwind v4 theme tokens
|
|
252
46
|
|
|
253
|
-
|
|
254
|
-
|
|
47
|
+
The components rely on the following CSS variables (defined inside
|
|
48
|
+
`@theme` in your app's globals.css):
|
|
255
49
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
72
|
+
## Breaking changes vs `@owlmeans/mui-panel`
|
|
328
73
|
|
|
329
|
-
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
|
|
99
|
+
Consumers that need the previous MUI behaviour should swap to
|
|
100
|
+
[`@owlmeans/mui-panel`](../mui-panel) — same exports, MUI-rendered.
|
|
365
101
|
|
|
366
|
-
|
|
102
|
+
## Public exports
|
|
367
103
|
|
|
368
|
-
```
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1194
|
-
|
|
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.
|