@owlmeans/web-panel 0.1.2 → 0.1.4
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 +4 -4
- 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 +19 -9
- 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
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
import type { FC } from 'react'
|
|
2
|
-
import { useCallback, useMemo } from 'react'
|
|
2
|
+
import { Children, useCallback, useMemo } from 'react'
|
|
3
3
|
import { FormProvider, useForm } from 'react-hook-form'
|
|
4
4
|
import { ajvResolver } from '@hookform/resolvers/ajv'
|
|
5
|
-
import Grid from '@mui/material/Grid'
|
|
6
|
-
import Card from '@mui/material/Card'
|
|
7
|
-
import CardContent from '@mui/material/CardContent'
|
|
8
|
-
import CardActions from '@mui/material/CardActions'
|
|
9
5
|
import { FormContext, schemaToFormDefault } from '@owlmeans/client-panel'
|
|
10
6
|
import type { JSONSchemaType } from 'ajv'
|
|
11
7
|
import Ajv from 'ajv'
|
|
12
8
|
import formatsPlugin from 'ajv-formats'
|
|
13
|
-
import type { SxProps } from '@mui/material/styles'
|
|
14
|
-
import { useTheme } from '@mui/material/styles'
|
|
15
9
|
import { SubmitButton } from './button/component.js'
|
|
16
10
|
import { useToggle } from '@owlmeans/client'
|
|
17
11
|
import { scalingToStyles } from '../helper.js'
|
|
18
12
|
import { ResilientError } from '@owlmeans/error'
|
|
19
13
|
import { Status } from '../status.js'
|
|
20
14
|
import type { WebFormProps } from './types.js'
|
|
15
|
+
import { Card, CardContent, CardFooter } from '@/components/ui/card'
|
|
16
|
+
import { cn } from '@/lib/utils'
|
|
21
17
|
|
|
22
18
|
const ajv = new Ajv({ coerceTypes: true })
|
|
23
19
|
formatsPlugin(ajv)
|
|
@@ -25,9 +21,8 @@ formatsPlugin(ajv)
|
|
|
25
21
|
export const Form: FC<WebFormProps> = (props) => {
|
|
26
22
|
const {
|
|
27
23
|
defaults, children, formRef, validation, name, horizontal, vertical,
|
|
28
|
-
decorate, onSubmit, i18n,
|
|
24
|
+
decorate, onSubmit, i18n, className, style
|
|
29
25
|
} = props
|
|
30
|
-
const theme = useTheme()
|
|
31
26
|
const _defaults = useMemo(
|
|
32
27
|
() => defaults ?? (validation != null ? schemaToFormDefault(validation) : undefined),
|
|
33
28
|
[name, defaults != null, validation != null]
|
|
@@ -61,35 +56,39 @@ export const Form: FC<WebFormProps> = (props) => {
|
|
|
61
56
|
})
|
|
62
57
|
}, [name])
|
|
63
58
|
|
|
64
|
-
|
|
65
59
|
if (formRef != null) {
|
|
66
60
|
formRef.current = { form, update, loader, error: setError }
|
|
67
61
|
}
|
|
68
62
|
|
|
69
|
-
const
|
|
63
|
+
const scaling = useMemo(() => scalingToStyles(horizontal, vertical), [horizontal, vertical])
|
|
70
64
|
|
|
71
|
-
const content = () =>
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
65
|
+
const content = () => (
|
|
66
|
+
<div className={cn('flex flex-col items-stretch justify-start gap-4', !decorate && scaling, !decorate && className)} style={!decorate ? style : undefined}>
|
|
67
|
+
{Array.isArray(children)
|
|
68
|
+
? Children.map(children, (child, index) => <div key={index}>{child}</div>)
|
|
69
|
+
: children
|
|
70
|
+
}
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
79
73
|
|
|
80
74
|
if (decorate === true) {
|
|
81
75
|
const root = form.getFieldState('root')
|
|
82
76
|
return <FormProvider {...form}>
|
|
83
77
|
<FormContext {...props} loader={loader}>
|
|
84
|
-
<Card
|
|
78
|
+
<Card className={cn(scaling, 'flex flex-col justify-between', className)} style={style}>
|
|
85
79
|
<CardContent>
|
|
86
80
|
{content()}
|
|
87
81
|
{root.invalid && root.error?.message &&
|
|
88
|
-
<
|
|
82
|
+
<div className="mt-4">
|
|
83
|
+
<Status ok={false} i18n={i18n} error={ResilientError.ensure(root.error.message)} />
|
|
84
|
+
</div>
|
|
85
|
+
}
|
|
89
86
|
</CardContent>
|
|
90
|
-
{onSubmit != null
|
|
91
|
-
<
|
|
92
|
-
|
|
87
|
+
{onSubmit != null && (
|
|
88
|
+
<CardFooter className="flex flex-row justify-end gap-2 pr-4 pb-2">
|
|
89
|
+
<SubmitButton loader={loader} onSubmit={async data => onSubmit(data, update)} />
|
|
90
|
+
</CardFooter>
|
|
91
|
+
)}
|
|
93
92
|
</Card>
|
|
94
93
|
</FormContext>
|
|
95
94
|
</FormProvider>
|
|
@@ -1,45 +1,54 @@
|
|
|
1
1
|
import type { FC } from 'react'
|
|
2
2
|
import { useFormContext, Controller } from 'react-hook-form'
|
|
3
|
-
import { TextInputProps } from './types.js'
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import { useClientFormContext } from '@owlmeans/client-panel'
|
|
3
|
+
import type { TextInputProps } from './types.js'
|
|
4
|
+
import { useFormError, useFormI18n, useClientFormContext } from '@owlmeans/client-panel'
|
|
5
|
+
import { Input } from '@/components/ui/input'
|
|
6
|
+
import { Label } from '@/components/ui/label'
|
|
8
7
|
|
|
9
8
|
export const TextInput: FC<TextInputProps> = ({ name, label, placeholder, hint, type, def, disableAutocomplete }) => {
|
|
10
9
|
const { control } = useFormContext()
|
|
11
10
|
const t = useFormI18n()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
if (typeof
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (typeof hint === 'boolean' && hint) {
|
|
24
|
-
|
|
25
|
-
} else {
|
|
26
|
-
hint = undefined
|
|
27
|
-
}
|
|
11
|
+
|
|
12
|
+
let resolvedLabel: string | undefined
|
|
13
|
+
let resolvedPlaceholder: string | undefined
|
|
14
|
+
let resolvedHint: string | undefined
|
|
15
|
+
|
|
16
|
+
if (typeof label === 'boolean' && label) resolvedLabel = t(`${name}.label`)
|
|
17
|
+
else if (typeof label === 'string') resolvedLabel = label
|
|
18
|
+
|
|
19
|
+
if (typeof placeholder === 'boolean' && placeholder) resolvedPlaceholder = t(`${name}.placeholder`)
|
|
20
|
+
else if (typeof placeholder === 'string') resolvedPlaceholder = placeholder
|
|
21
|
+
|
|
22
|
+
if (typeof hint === 'boolean' && hint) resolvedHint = t(`${name}.hint`)
|
|
23
|
+
else if (typeof hint === 'string') resolvedHint = hint
|
|
28
24
|
|
|
29
25
|
return <Controller control={control} name={name} defaultValue={def} render={
|
|
30
26
|
({ field, fieldState }) => {
|
|
31
27
|
const error = useFormError(name, fieldState.error)
|
|
32
28
|
const { loader } = useClientFormContext()
|
|
29
|
+
const disabled = loader != null && loader.opened === true
|
|
33
30
|
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
31
|
+
return (
|
|
32
|
+
<div className="flex w-full flex-col gap-1.5">
|
|
33
|
+
{resolvedLabel != null && <Label htmlFor={name}>{resolvedLabel}</Label>}
|
|
34
|
+
<Input
|
|
35
|
+
id={name}
|
|
36
|
+
{...field}
|
|
37
|
+
type={type ?? 'text'}
|
|
38
|
+
placeholder={resolvedPlaceholder}
|
|
39
|
+
autoComplete={disableAutocomplete ? 'off' : 'on'}
|
|
40
|
+
disabled={disabled}
|
|
41
|
+
aria-invalid={fieldState.error != null}
|
|
42
|
+
/>
|
|
43
|
+
{(error != null || resolvedHint != null) && (
|
|
44
|
+
<p className={
|
|
45
|
+
error != null
|
|
46
|
+
? 'text-sm text-destructive'
|
|
47
|
+
: 'text-sm text-muted-foreground'
|
|
48
|
+
}>{error ?? resolvedHint}</p>
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
)
|
|
43
52
|
}
|
|
44
53
|
} />
|
|
45
54
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { FormProps } from '@owlmeans/client-panel'
|
|
2
|
+
import type { CSSProperties } from 'react'
|
|
3
3
|
|
|
4
4
|
export interface WebFormProps extends FormProps {
|
|
5
|
-
|
|
5
|
+
className?: string
|
|
6
|
+
style?: CSSProperties
|
|
6
7
|
}
|
package/src/components/helper.ts
CHANGED
|
@@ -1,70 +1,85 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { useTheme } from '@mui/material/styles'
|
|
3
|
-
import useMediaQuery from '@mui/material/useMediaQuery'
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
4
2
|
import { BlockScaling } from '@owlmeans/client-panel'
|
|
5
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Map MUI's previous `scalingToStyles(horizontal, vertical, theme): SxProps`
|
|
6
|
+
* to a Tailwind utility class composition string. Consumers compose this
|
|
7
|
+
* with their own classes via `cn()` or template literals.
|
|
8
|
+
*
|
|
9
|
+
* Breakpoint semantics mirror the old MUI implementation:
|
|
10
|
+
* - Half: capped width/height on >=md, expanded on <md, `flex-grow: 1`
|
|
11
|
+
* - Wide: 10% horizontal/vertical margin, `flex-grow: 1`
|
|
12
|
+
* - Full: fill axis, `flex-grow: 1`
|
|
13
|
+
*/
|
|
6
14
|
export const scalingToStyles = (
|
|
7
15
|
horizontal?: BlockScaling,
|
|
8
|
-
vertical?: BlockScaling
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
vertical?: BlockScaling
|
|
17
|
+
): string => {
|
|
18
|
+
const parts: string[] = []
|
|
19
|
+
|
|
12
20
|
switch (horizontal) {
|
|
13
21
|
case BlockScaling.Half:
|
|
14
|
-
|
|
15
|
-
maxWidth: '50%',
|
|
16
|
-
[theme?.breakpoints.down('md') ?? 'xs']: {
|
|
17
|
-
maxWidth: '90%'
|
|
18
|
-
},
|
|
19
|
-
flexGrow: 1
|
|
20
|
-
})
|
|
22
|
+
parts.push('max-w-[90%]', 'md:max-w-[50%]', 'grow')
|
|
21
23
|
break
|
|
22
24
|
case BlockScaling.Wide:
|
|
23
|
-
|
|
25
|
+
parts.push('mx-[10%]', 'grow')
|
|
24
26
|
break
|
|
25
27
|
case BlockScaling.Full:
|
|
26
|
-
|
|
28
|
+
parts.push('grow')
|
|
27
29
|
break
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
switch (vertical) {
|
|
31
33
|
case BlockScaling.Half:
|
|
32
|
-
|
|
33
|
-
maxHeight: '50%',
|
|
34
|
-
[theme?.breakpoints.down('md') ?? 'xs']: {
|
|
35
|
-
maxHeight: '90%'
|
|
36
|
-
},
|
|
37
|
-
flexGrow: 1
|
|
38
|
-
})
|
|
34
|
+
parts.push('max-h-[90%]', 'md:max-h-[50%]', 'grow')
|
|
39
35
|
break
|
|
40
36
|
case BlockScaling.Wide:
|
|
41
|
-
|
|
37
|
+
parts.push('my-[10%]', 'grow')
|
|
42
38
|
break
|
|
43
39
|
case BlockScaling.Full:
|
|
44
|
-
|
|
40
|
+
parts.push('h-full', 'grow')
|
|
45
41
|
break
|
|
46
42
|
}
|
|
47
43
|
|
|
48
|
-
return
|
|
44
|
+
return parts.join(' ')
|
|
49
45
|
}
|
|
50
46
|
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Tailwind default breakpoints — kept stable across consumers. If a consumer
|
|
49
|
+
* has customised Tailwind breakpoints in their app config, override this
|
|
50
|
+
* via a wrapping hook in the app.
|
|
51
|
+
*/
|
|
52
|
+
const BREAKPOINTS: Array<{ name: string, min: number, max: number }> = [
|
|
53
|
+
{ name: 'xs', min: 0, max: 639 },
|
|
54
|
+
{ name: 'sm', min: 640, max: 767 },
|
|
55
|
+
{ name: 'md', min: 768, max: 1023 },
|
|
56
|
+
{ name: 'lg', min: 1024, max: 1279 },
|
|
57
|
+
{ name: 'xl', min: 1280, max: Number.POSITIVE_INFINITY },
|
|
58
|
+
]
|
|
53
59
|
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
const matchBreakpoint = (width: number): string => {
|
|
61
|
+
for (const bp of BREAKPOINTS) {
|
|
62
|
+
if (width >= bp.min && width <= bp.max) {
|
|
63
|
+
return bp.name
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return 'xs'
|
|
67
|
+
}
|
|
60
68
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (isXl) currentBreakpoint = 'xl'
|
|
69
|
+
export const useBreakPoint = (): string => {
|
|
70
|
+
const [bp, setBp] = useState<string>(() =>
|
|
71
|
+
typeof window === 'undefined' ? 'lg' : matchBreakpoint(window.innerWidth)
|
|
72
|
+
)
|
|
66
73
|
|
|
67
|
-
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (typeof window === 'undefined') return
|
|
76
|
+
const onResize = () => setBp(matchBreakpoint(window.innerWidth))
|
|
77
|
+
onResize()
|
|
78
|
+
window.addEventListener('resize', onResize)
|
|
79
|
+
return () => window.removeEventListener('resize', onResize)
|
|
80
|
+
}, [])
|
|
81
|
+
|
|
82
|
+
return bp
|
|
68
83
|
}
|
|
69
84
|
|
|
70
85
|
export const useMapBreakpoint = <T>(map: Record<string, T>, def?: T, breakpoint?: string): T => {
|
|
@@ -74,6 +89,5 @@ export const useMapBreakpoint = <T>(map: Record<string, T>, def?: T, breakpoint?
|
|
|
74
89
|
if (result == null) {
|
|
75
90
|
throw new SyntaxError(`Breakpoint should always return value. We have ${breakpoint}, but ${Object.keys(map).join(', ')} are available`)
|
|
76
91
|
}
|
|
77
|
-
|
|
78
|
-
return result
|
|
92
|
+
return result as T
|
|
79
93
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { FC } from 'react'
|
|
2
2
|
import type { LayoutProps } from './types.js'
|
|
3
|
-
import Box from '@mui/material/Box'
|
|
4
3
|
|
|
5
|
-
export const Layout: FC<LayoutProps> = ({ children }) => {
|
|
6
|
-
return <
|
|
4
|
+
export const Layout: FC<LayoutProps> = ({ children, className, style }) => {
|
|
5
|
+
return <div className={className} style={style}>{children}</div>
|
|
7
6
|
}
|
package/src/components/link.tsx
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { usePanelI18n } from '@owlmeans/client-panel'
|
|
2
2
|
import type { FC } from 'react'
|
|
3
3
|
import type { LinkProps } from './types.js'
|
|
4
|
-
import MUILink from '@mui/material/Link'
|
|
5
|
-
import type { TypographyOwnProps } from '@mui/material'
|
|
6
4
|
import { useValue } from '@owlmeans/client'
|
|
7
5
|
import { useContext } from '@owlmeans/web-client'
|
|
8
|
-
import type {
|
|
6
|
+
import type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
|
|
7
|
+
import { cn } from '@/lib/utils'
|
|
9
8
|
|
|
10
|
-
export const Link: FC<LinkProps> = ({ src, module, name,
|
|
9
|
+
export const Link: FC<LinkProps> = ({ src, module, name, children, center, open, className, style }) => {
|
|
11
10
|
const t = usePanelI18n()
|
|
12
11
|
const context = useContext()
|
|
13
12
|
|
|
@@ -16,9 +15,8 @@ export const Link: FC<LinkProps> = ({ src, module, name, variant, children, cent
|
|
|
16
15
|
return src
|
|
17
16
|
}
|
|
18
17
|
if (module != null) {
|
|
19
|
-
module = typeof module === 'string' ? context.module<
|
|
18
|
+
module = typeof module === 'string' ? context.module<ClientEntrypoint<string>>(module) : module
|
|
20
19
|
const [url] = await module.call<string>()
|
|
21
|
-
|
|
22
20
|
return url
|
|
23
21
|
}
|
|
24
22
|
return null
|
|
@@ -27,8 +25,20 @@ export const Link: FC<LinkProps> = ({ src, module, name, variant, children, cent
|
|
|
27
25
|
const label = name != null
|
|
28
26
|
? t(name)
|
|
29
27
|
: children != null || module == null
|
|
30
|
-
? undefined
|
|
28
|
+
? undefined
|
|
29
|
+
: t(`modules.${typeof module === 'string' ? module : module.alias}`)
|
|
31
30
|
const target = open ? '_blank' : undefined
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
const rel = open ? 'noopener noreferrer' : undefined
|
|
32
|
+
|
|
33
|
+
return <a
|
|
34
|
+
href={href ?? undefined}
|
|
35
|
+
target={target}
|
|
36
|
+
rel={rel}
|
|
37
|
+
className={cn(
|
|
38
|
+
'text-primary underline-offset-4 hover:underline',
|
|
39
|
+
center && 'text-center',
|
|
40
|
+
className
|
|
41
|
+
)}
|
|
42
|
+
style={style}
|
|
43
|
+
>{label ?? children}</a>
|
|
34
44
|
}
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import type { FC } from 'react'
|
|
2
|
-
import { useMemo } from 'react'
|
|
3
2
|
import type { PanelAppProps } from './types.js'
|
|
4
3
|
|
|
5
4
|
import { App } from '@owlmeans/client'
|
|
6
5
|
import { I18nContext } from '@owlmeans/client-i18n'
|
|
7
|
-
import
|
|
8
|
-
import { createTheme, ThemeProvider } from '@mui/material/styles'
|
|
6
|
+
import { cn } from '@/lib/utils'
|
|
9
7
|
|
|
10
|
-
export const PanelApp: FC<PanelAppProps> = ({ context, provide, children,
|
|
11
|
-
|
|
12
|
-
return <ThemeProvider theme={theme}>
|
|
8
|
+
export const PanelApp: FC<PanelAppProps> = ({ context, provide, children, rootClassName }) => {
|
|
9
|
+
return <div className={cn('min-h-screen bg-background text-foreground', rootClassName)}>
|
|
13
10
|
<I18nContext config={context.cfg}>
|
|
14
11
|
<App context={context} provide={provide}>
|
|
15
|
-
<CssBaseline />
|
|
16
12
|
{children}
|
|
17
|
-
</App
|
|
13
|
+
</App>
|
|
18
14
|
</I18nContext>
|
|
19
|
-
</
|
|
15
|
+
</div>
|
|
20
16
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import type { Theme } from '@mui/material/styles'
|
|
2
1
|
import type { AppProps } from '@owlmeans/client'
|
|
3
2
|
|
|
4
3
|
export interface PanelAppProps extends AppProps {
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Optional CSS class added to the outermost wrapper. Use to scope
|
|
6
|
+
* Tailwind theme overrides (e.g. `'dark'` or a custom theme class).
|
|
7
|
+
* Replaces the legacy MUI `theme?: Theme` prop.
|
|
8
|
+
*/
|
|
9
|
+
rootClassName?: string
|
|
6
10
|
}
|
|
@@ -3,21 +3,32 @@ import { useMemo } from 'react'
|
|
|
3
3
|
import type { StatusProps } from './types.js'
|
|
4
4
|
import { usePanelI18n } from '@owlmeans/client-panel'
|
|
5
5
|
import { ResilientError } from '@owlmeans/error'
|
|
6
|
-
import Alert from '
|
|
6
|
+
import { Alert, AlertDescription } from '@/components/ui/alert'
|
|
7
7
|
|
|
8
|
-
const prepareMessage = (msg: string) => msg.replace(
|
|
8
|
+
const prepareMessage = (msg: string) => msg.replace(/:/g, '.')
|
|
9
|
+
|
|
10
|
+
const variantToAlert = (variant: string): 'default' | 'destructive' | 'success' => {
|
|
11
|
+
if (variant === 'error' || variant === 'destructive') return 'destructive'
|
|
12
|
+
if (variant === 'success') return 'success'
|
|
13
|
+
return 'default'
|
|
14
|
+
}
|
|
9
15
|
|
|
10
16
|
export const Status: FC<StatusProps> = ({ ok, name, i18n, children, variant, message, error }) => {
|
|
11
17
|
variant = useMemo(() => variant ?? (ok ? 'success' : 'error'), [ok, variant])
|
|
12
18
|
const t = usePanelI18n(name ?? variant, i18n)
|
|
13
19
|
message = useMemo(() => {
|
|
14
20
|
const resilient = error != null ? ResilientError.ensure(error) : null
|
|
15
|
-
return message != null
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
return message != null
|
|
22
|
+
? t(message)
|
|
23
|
+
: resilient != null
|
|
24
|
+
? t([
|
|
25
|
+
`${resilient.type}.${prepareMessage(resilient.message)}`,
|
|
26
|
+
prepareMessage(resilient.message)
|
|
27
|
+
])
|
|
28
|
+
: t(variant as string)
|
|
21
29
|
}, [message, error?.name, ok, variant])
|
|
22
|
-
|
|
30
|
+
|
|
31
|
+
return <Alert variant={variantToAlert(variant as string)}>
|
|
32
|
+
<AlertDescription>{children ?? message}</AlertDescription>
|
|
33
|
+
</Alert>
|
|
23
34
|
}
|
package/src/components/text.tsx
CHANGED
|
@@ -1,14 +1,33 @@
|
|
|
1
|
-
|
|
2
1
|
import { usePanelI18n } from '@owlmeans/client-panel'
|
|
3
|
-
import type { FC } from 'react'
|
|
4
|
-
import type { TextProps } from './types.js'
|
|
5
|
-
import
|
|
6
|
-
import type { TypographyOwnProps } from '@mui/material'
|
|
2
|
+
import type { FC, JSX } from 'react'
|
|
3
|
+
import type { TextProps, TextVariant } from './types.js'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const variantClasses: Record<TextVariant, string> = {
|
|
7
|
+
h1: 'scroll-m-20 text-4xl font-extrabold tracking-tight',
|
|
8
|
+
h2: 'scroll-m-20 text-3xl font-semibold tracking-tight',
|
|
9
|
+
h3: 'scroll-m-20 text-2xl font-semibold tracking-tight',
|
|
10
|
+
h4: 'scroll-m-20 text-xl font-semibold tracking-tight',
|
|
11
|
+
p: 'leading-7',
|
|
12
|
+
lead: 'text-xl text-muted-foreground',
|
|
13
|
+
large: 'text-lg font-semibold',
|
|
14
|
+
small: 'text-sm font-medium leading-none',
|
|
15
|
+
muted: 'text-sm text-muted-foreground',
|
|
16
|
+
blockquote: 'mt-6 border-l-2 pl-6 italic',
|
|
17
|
+
}
|
|
10
18
|
|
|
19
|
+
const variantTag = (variant: TextVariant, nested: boolean): keyof JSX.IntrinsicElements => {
|
|
20
|
+
if (nested) return 'span'
|
|
21
|
+
if (variant === 'h1' || variant === 'h2' || variant === 'h3' || variant === 'h4') return variant
|
|
22
|
+
if (variant === 'blockquote') return 'blockquote'
|
|
23
|
+
return 'p'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const Text: FC<TextProps> = ({ variant = 'p', name, children, center, className, style, nested = false, i18n }) => {
|
|
27
|
+
const t = usePanelI18n(undefined, i18n)
|
|
11
28
|
const label = name != null ? t(name) : undefined
|
|
12
|
-
|
|
13
|
-
|
|
29
|
+
const Tag = variantTag(variant, nested)
|
|
30
|
+
const cls = cn(variantClasses[variant], center && 'text-center', className)
|
|
31
|
+
|
|
32
|
+
return <Tag className={cls} style={style}>{label ?? children}</Tag>
|
|
14
33
|
}
|
package/src/components/types.ts
CHANGED
|
@@ -1,28 +1,40 @@
|
|
|
1
|
-
import type { SxProps } from '@mui/material/styles'
|
|
2
1
|
import type { I18nProps } from '@owlmeans/client-i18n'
|
|
3
2
|
import type { BlockScaling } from '@owlmeans/client-panel'
|
|
4
|
-
import type { FC, PropsWithChildren } from 'react'
|
|
5
|
-
import type {
|
|
6
|
-
import type { ClientModule } from '@owlmeans/client-module'
|
|
3
|
+
import type { CSSProperties, FC, PropsWithChildren } from 'react'
|
|
4
|
+
import type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Shadcn `Typography`-equivalent text variants. Each value maps to a Tailwind
|
|
8
|
+
* class composition inside `Text` / `Link`. This replaces MUI's
|
|
9
|
+
* `TypographyOwnProps['variant']`.
|
|
10
|
+
*/
|
|
11
|
+
export type TextVariant =
|
|
12
|
+
| 'h1' | 'h2' | 'h3' | 'h4'
|
|
13
|
+
| 'p' | 'lead' | 'large' | 'small' | 'muted' | 'blockquote'
|
|
14
|
+
|
|
15
|
+
export interface StyledProps {
|
|
16
|
+
/** Tailwind utility classes appended after the component's base classes. */
|
|
17
|
+
className?: string
|
|
18
|
+
/** Raw inline CSS — escape hatch for non-class styling. */
|
|
19
|
+
style?: CSSProperties
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface BlockProps extends PropsWithChildren<I18nProps>, StyledProps {
|
|
9
23
|
horizontal?: BlockScaling
|
|
10
24
|
vertical?: BlockScaling
|
|
11
25
|
Actions?: FC
|
|
12
|
-
styles?: SxProps
|
|
13
26
|
}
|
|
14
27
|
|
|
15
|
-
export interface TextProps extends PropsWithChildren<I18nProps
|
|
28
|
+
export interface TextProps extends PropsWithChildren<I18nProps>, StyledProps {
|
|
16
29
|
name?: string
|
|
17
|
-
variant?:
|
|
30
|
+
variant?: TextVariant
|
|
18
31
|
center?: boolean
|
|
19
|
-
styles?: SxProps
|
|
20
32
|
nested?: boolean
|
|
21
33
|
}
|
|
22
34
|
|
|
23
35
|
export interface LinkProps extends TextProps {
|
|
24
36
|
src?: string
|
|
25
|
-
module?: string |
|
|
37
|
+
module?: string | ClientEntrypoint
|
|
26
38
|
open?: boolean
|
|
27
39
|
}
|
|
28
40
|
|
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
import type { FC } from 'react'
|
|
2
2
|
import { ImageUploader as Uploader } from '@owlmeans/web-client'
|
|
3
|
-
import
|
|
3
|
+
import { ImagePlus } from 'lucide-react'
|
|
4
4
|
import type { ImageUploaderProps } from './types.js'
|
|
5
|
-
import
|
|
6
|
-
|
|
5
|
+
import { cn } from '@/lib/utils'
|
|
6
|
+
|
|
7
|
+
// Tailwind responsive sizes — mirrors the previous MUI breakpoint object.
|
|
8
|
+
// xs: 60/65px md: 120/125px lg: 200/205px
|
|
9
|
+
const wrapperClasses = 'w-[65px] h-[65px] md:w-[125px] md:h-[125px] lg:w-[205px] lg:h-[205px]'
|
|
10
|
+
const previewClasses = 'max-w-[60px] max-h-[60px] md:max-w-[120px] md:max-h-[120px] lg:max-w-[200px] lg:max-h-[200px]'
|
|
11
|
+
const iconClasses = 'size-[60px] md:size-[120px] lg:size-[200px] text-primary'
|
|
7
12
|
|
|
8
13
|
export const ImageUploader: FC<ImageUploaderProps> = ({ Root, rootProps, previewUrl, ...others }) => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}} {...others}>
|
|
21
|
-
{
|
|
22
|
-
previewUrl
|
|
23
|
-
|
|
24
|
-
sx={{ maxWidth: previewSize, maxHeight: previewSize }} />
|
|
25
|
-
: <AddPhotoAlternateOutlinedIcon sx={{ fontSize: previewSize }} color="primary" />
|
|
14
|
+
const DefaultRoot: FC<any> = ({ children, className, ...rest }) => (
|
|
15
|
+
<div
|
|
16
|
+
{...rest}
|
|
17
|
+
className={cn(
|
|
18
|
+
'flex items-center justify-center rounded-md border bg-card shadow-sm cursor-pointer',
|
|
19
|
+
wrapperClasses,
|
|
20
|
+
className
|
|
21
|
+
)}
|
|
22
|
+
>{children}</div>
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
return <Uploader Root={Root ?? DefaultRoot} rootProps={rootProps} {...others}>
|
|
26
|
+
{previewUrl != null
|
|
27
|
+
? <img src={previewUrl} className={previewClasses} />
|
|
28
|
+
: <ImagePlus className={iconClasses} aria-hidden />
|
|
26
29
|
}
|
|
27
30
|
</Uploader>
|
|
28
31
|
}
|
|
29
|
-
|
|
30
|
-
const previewSize = { xs: 60, md: 120, lg: 200 }
|
|
31
|
-
const wrapperSize = { xs: 65, md: 125, lg: 205 }
|
package/src/exports.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
export { handler, useNavigate, useValue, useModule } from '@owlmeans/client'
|
|
3
3
|
export { config } from '@owlmeans/client-context'
|
|
4
4
|
export { service } from '@owlmeans/config'
|
|
5
|
-
export { guard, parent } from '@owlmeans/
|
|
5
|
+
export { guard, parent } from '@owlmeans/entrypoint'
|
|
6
6
|
export { addWebService } from '@owlmeans/client-config'
|
|
7
|
-
export {
|
|
8
|
-
export type {
|
|
7
|
+
export { entrypoint, elevate, provideRequest, stab } from '@owlmeans/client-entrypoint'
|
|
8
|
+
export type { ClientEntrypoint as Module } from '@owlmeans/client-entrypoint'
|
|
9
9
|
export { route as croute } from '@owlmeans/client-route'
|
|
10
10
|
export { route, frontend } from '@owlmeans/route'
|
|
11
11
|
export { DEFAULT_ALIAS as DAUTH_GUARD, setupExternalAuthentication } from '@owlmeans/client-auth'
|