@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
@@ -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, styles
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 style: SxProps = useMemo(() => scalingToStyles(horizontal, vertical, theme), [horizontal])
63
+ const scaling = useMemo(() => scalingToStyles(horizontal, vertical), [horizontal, vertical])
70
64
 
71
- const content = () =>
72
- <Grid container direction="column" justifyContent="flex-start" alignItems="stretch"
73
- rowSpacing={2} sx={!decorate ? style : {}}>{
74
- Array.isArray(children)
75
- ? children.map((child, index) =>
76
- <Grid key={index}>{child}</Grid>
77
- ) : children
78
- }</Grid>
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 sx={{ ...style, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', ...styles } as SxProps}>
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
- <Status ok={false} i18n={i18n} error={ResilientError.ensure(root.error.message)} />}
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 ? <CardActions sx={{ flexDirection: 'row', justifyContent: 'flex-end', pr: 2, pb: 2 }}>
91
- <SubmitButton loader={loader} onSubmit={async data => onSubmit(data, update)} />
92
- </CardActions> : undefined}
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 TextField from '@mui/material/TextField'
6
- import { useFormError, useFormI18n } from '@owlmeans/client-panel'
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
- const key = name
13
- if (typeof label === 'boolean' && label) {
14
- label = t(`${key}.label`)
15
- } else {
16
- label = undefined
17
- }
18
- if (typeof placeholder === 'boolean' && placeholder) {
19
- placeholder = t(`${key}.placeholder`)
20
- } else {
21
- placeholder = undefined
22
- }
23
- if (typeof hint === 'boolean' && hint) {
24
- hint = t(`${key}.hint`)
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 <TextField fullWidth {...field}
35
- error={fieldState.error != null}
36
- disabled={loader != null && loader.opened === true}
37
- label={label}
38
- type={type ?? 'text'}
39
- placeholder={placeholder}
40
- autoComplete={disableAutocomplete ? 'off' : 'on'}
41
- helperText={error ?? hint}
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 { SxProps } from '@mui/material/styles'
2
- import type { FormProps } from '@owlmeans/client-panel'
1
+ import type { FormProps } from '@owlmeans/client-panel'
2
+ import type { CSSProperties } from 'react'
3
3
 
4
4
  export interface WebFormProps extends FormProps {
5
- styles?: SxProps
5
+ className?: string
6
+ style?: CSSProperties
6
7
  }
@@ -1,70 +1,85 @@
1
- import type { SxProps, Theme } from '@mui/material/styles'
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
- theme?: Theme
10
- ): SxProps => {
11
- const style: SxProps = {}
16
+ vertical?: BlockScaling
17
+ ): string => {
18
+ const parts: string[] = []
19
+
12
20
  switch (horizontal) {
13
21
  case BlockScaling.Half:
14
- Object.assign(style, {
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
- Object.assign(style, { mx: '10%', flexGrow: 1 })
25
+ parts.push('mx-[10%]', 'grow')
24
26
  break
25
27
  case BlockScaling.Full:
26
- Object.assign(style, { flexGrow: 1 })
28
+ parts.push('grow')
27
29
  break
28
30
  }
29
31
 
30
32
  switch (vertical) {
31
33
  case BlockScaling.Half:
32
- Object.assign(style, {
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
- Object.assign(style, { my: '10%', flexGrow: 1 })
37
+ parts.push('my-[10%]', 'grow')
42
38
  break
43
39
  case BlockScaling.Full:
44
- Object.assign(style, { height: '100%', flexGrow: 1 })
40
+ parts.push('h-full', 'grow')
45
41
  break
46
42
  }
47
43
 
48
- return style
44
+ return parts.join(' ')
49
45
  }
50
46
 
51
- export const useBreakPoint = () => {
52
- const theme = useTheme()
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
- // Define breakpoints
55
- const isXs = useMediaQuery(theme.breakpoints.only('xs'))
56
- const isSm = useMediaQuery(theme.breakpoints.only('sm'))
57
- const isMd = useMediaQuery(theme.breakpoints.only('md'))
58
- const isLg = useMediaQuery(theme.breakpoints.only('lg'))
59
- const isXl = useMediaQuery(theme.breakpoints.only('xl'))
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
- let currentBreakpoint = isXs ? 'xs' : 'unknown' // Default value
62
- if (isSm) currentBreakpoint = 'sm'
63
- if (isMd) currentBreakpoint = 'md'
64
- if (isLg) currentBreakpoint = 'lg'
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
- return currentBreakpoint
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 <Box>{children}</Box>
4
+ export const Layout: FC<LayoutProps> = ({ children, className, style }) => {
5
+ return <div className={className} style={style}>{children}</div>
7
6
  }
@@ -1,4 +1,6 @@
1
- import type { PropsWithChildren } from 'react'
1
+ import type { CSSProperties, PropsWithChildren } from 'react'
2
2
 
3
3
  export interface LayoutProps extends PropsWithChildren {
4
+ className?: string
5
+ style?: CSSProperties
4
6
  }
@@ -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
6
  import type { ClientModule } from '@owlmeans/client-module'
7
+ import { cn } from '@/lib/utils'
9
8
 
10
- export const Link: FC<LinkProps> = ({ src, module, name, variant, children, center, open, styles }) => {
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
 
@@ -18,7 +17,6 @@ export const Link: FC<LinkProps> = ({ src, module, name, variant, children, cent
18
17
  if (module != null) {
19
18
  module = typeof module === 'string' ? context.module<ClientModule<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 : t(`modules.${typeof module == 'string' ? module : module.alias}`)
28
+ ? undefined
29
+ : t(`modules.${typeof module === 'string' ? module : module.alias}`)
31
30
  const target = open ? '_blank' : undefined
32
- return <MUILink href={href ?? undefined} target={target} variant={variant as TypographyOwnProps['variant']}
33
- sx={{ textAlign: center ? 'center' : 'inherit', ...styles }}>{label ?? children}</MUILink>
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 CssBaseline from '@mui/material/CssBaseline'
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, theme }) => {
11
- theme = useMemo(() => theme ?? createTheme(), [theme])
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
- </ThemeProvider>
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
- theme?: Theme
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 '@mui/material/Alert'
6
+ import { Alert, AlertDescription } from '@/components/ui/alert'
7
7
 
8
- const prepareMessage = (msg: string) => msg.replace(/\:/g, '.')
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 ? t(message) : resilient != null ? t(
16
- [
17
- `${resilient.type}.${prepareMessage(resilient.message)}`,
18
- prepareMessage(resilient.message)
19
- ],
20
- ) : t(variant)
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
- return <Alert severity={variant as any}>{children ?? message}</Alert>
30
+
31
+ return <Alert variant={variantToAlert(variant as string)}>
32
+ <AlertDescription>{children ?? message}</AlertDescription>
33
+ </Alert>
23
34
  }
@@ -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 Typography from '@mui/material/Typography'
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
- export const Text: FC<TextProps> = ({ variant, name, children, center, styles, nested, i18n }) => {
9
- const t = usePanelI18n(undefined, i18n)
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
- return <Typography component={nested ? 'span' : 'p'} variant={variant as TypographyOwnProps['variant']}
13
- sx={{ textAlign: center ? 'center' : 'inherit', ...styles }}>{label ?? children}</Typography>
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
  }
@@ -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 { TypographyOwnProps } from '@mui/material'
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
- export interface BlockProps extends PropsWithChildren<I18nProps> {
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?: TypographyOwnProps['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 | ClientModule
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 Paper from '@mui/material/Paper'
3
+ import { ImagePlus } from 'lucide-react'
4
4
  import type { ImageUploaderProps } from './types.js'
5
- import AddPhotoAlternateOutlinedIcon from '@mui/icons-material/AddPhotoAlternateOutlined';
6
- import Box from '@mui/material/Box'
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
- return <Uploader Root={Root ?? Paper} rootProps={{
10
- elevation: 2,
11
- sx: {
12
- width: wrapperSize,
13
- height: wrapperSize,
14
- display: "flex",
15
- justifyContent: "center",
16
- alignItems: "center",
17
- cursor: 'pointer',
18
- },
19
- ...rootProps
20
- }} {...others}>
21
- {
22
- previewUrl != null
23
- ? <Box component="img" src={previewUrl}
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/module'
5
+ export { guard, parent } from '@owlmeans/entrypoint'
6
6
  export { addWebService } from '@owlmeans/client-config'
7
- export { module, elevate, provideRequest, stab } from '@owlmeans/client-module'
8
- export type { ClientModule as Module } from '@owlmeans/client-module'
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'