@nan0web/ui-payload 3.3.1 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nan0web/ui-payload",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"description": "NaN0Web Universal Payload CMS UI Components Package (ImageCell, MapCell, BooleanCell, richtext)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"types": "./types/index.d.ts",
|
|
8
8
|
"files": [
|
|
9
9
|
"src/**/*.js",
|
|
10
|
+
"src/**/*.jsx",
|
|
10
11
|
"src/**/*.ts",
|
|
11
12
|
"src/**/*.tsx",
|
|
12
13
|
"!src/**/*.spec.js",
|
|
14
|
+
"!src/**/*.spec.jsx",
|
|
13
15
|
"!src/**/*.spec.tsx",
|
|
14
16
|
"types/**/*.d.ts"
|
|
15
17
|
],
|
|
@@ -22,6 +24,10 @@
|
|
|
22
24
|
"types": "./types/index.d.ts",
|
|
23
25
|
"import": "./src/index.js"
|
|
24
26
|
},
|
|
27
|
+
"./access": {
|
|
28
|
+
"types": "./types/access.d.ts",
|
|
29
|
+
"import": "./src/access.js"
|
|
30
|
+
},
|
|
25
31
|
"./components/*": {
|
|
26
32
|
"types": "./types/components/*.d.ts",
|
|
27
33
|
"import": "./src/components/*.js"
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { useTranslation } from '@payloadcms/ui'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Object} BooleanCellProps
|
|
8
|
+
* @property {boolean} [cellData]
|
|
9
|
+
* @property {boolean} [value]
|
|
10
|
+
* @property {any} [field]
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Custom BooleanCell renderer for Payload CMS admin tables.
|
|
15
|
+
* @param {BooleanCellProps} props
|
|
16
|
+
* @returns {React.JSX.Element}
|
|
17
|
+
*/
|
|
18
|
+
export function BooleanCell({ cellData, value, field }) {
|
|
19
|
+
const { i18n } = useTranslation()
|
|
20
|
+
const lang = i18n?.language?.toLowerCase().startsWith('en') ? 'en' : 'uk'
|
|
21
|
+
const val = Boolean(cellData ?? value)
|
|
22
|
+
|
|
23
|
+
const isHiddenField = field?.name?.toLowerCase() === 'hidden'
|
|
24
|
+
const customLabels = field?.admin?.custom?.labels
|
|
25
|
+
|
|
26
|
+
const defaultLabels = isHiddenField
|
|
27
|
+
? { true: { uk: '🙈 Приховано', en: '🙈 Hidden' }, false: { uk: '🌐 Активно', en: '🌐 Active' } }
|
|
28
|
+
: { true: { uk: '✅ Так', en: '✅ Yes' }, false: { uk: '❌ Ні', en: '❌ No' } }
|
|
29
|
+
|
|
30
|
+
const labels = customLabels || defaultLabels
|
|
31
|
+
const text = labels[val ? 'true' : 'false']?.[lang] || (val ? 'Yes' : 'No')
|
|
32
|
+
|
|
33
|
+
const isWarning = isHiddenField ? val : !val
|
|
34
|
+
const variant = isWarning ? 'warning' : 'success'
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<span
|
|
38
|
+
className={`badge bg-${variant}-subtle text-${variant}-emphasis border border-${variant}-subtle`}
|
|
39
|
+
style={{
|
|
40
|
+
display: 'inline-flex',
|
|
41
|
+
alignItems: 'center',
|
|
42
|
+
gap: '4px',
|
|
43
|
+
padding: '3px 8px',
|
|
44
|
+
borderRadius: '12px',
|
|
45
|
+
fontSize: '11px',
|
|
46
|
+
fontWeight: 600,
|
|
47
|
+
backgroundColor: isWarning
|
|
48
|
+
? 'var(--bs-warning-bg-subtle, rgba(255, 193, 7, 0.15))'
|
|
49
|
+
: 'var(--bs-success-bg-subtle, rgba(25, 135, 84, 0.15))',
|
|
50
|
+
color: isWarning
|
|
51
|
+
? 'var(--bs-warning-text-emphasis, #664d03)'
|
|
52
|
+
: 'var(--bs-success-text-emphasis, #0a3622)',
|
|
53
|
+
borderColor: isWarning
|
|
54
|
+
? 'var(--bs-warning-border-subtle, rgba(255, 193, 7, 0.3))'
|
|
55
|
+
: 'var(--bs-success-border-subtle, rgba(25, 135, 84, 0.3))',
|
|
56
|
+
}}
|
|
57
|
+
suppressHydrationWarning
|
|
58
|
+
>
|
|
59
|
+
{text}
|
|
60
|
+
</span>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default BooleanCell
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} ImageCellProps
|
|
7
|
+
* @property {string | { url?: string; thumbnailURL?: string }} [cellData]
|
|
8
|
+
* @property {string | { url?: string; thumbnailURL?: string }} [value]
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Image preview cell for Payload CMS admin tables.
|
|
13
|
+
* @param {ImageCellProps} props
|
|
14
|
+
* @returns {React.JSX.Element | null}
|
|
15
|
+
*/
|
|
16
|
+
export function ImageCell(props) {
|
|
17
|
+
const [hasError, setHasError] = useState(false)
|
|
18
|
+
const rawValue = props.cellData || props.value
|
|
19
|
+
|
|
20
|
+
if (!rawValue || hasError) {
|
|
21
|
+
return (
|
|
22
|
+
<div
|
|
23
|
+
style={{
|
|
24
|
+
display: 'inline-flex',
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
justifyContent: 'center',
|
|
27
|
+
width: '192px',
|
|
28
|
+
height: '108px',
|
|
29
|
+
borderRadius: '6px',
|
|
30
|
+
background: 'rgba(255, 255, 255, 0.05)',
|
|
31
|
+
border: '1px solid rgba(255, 255, 255, 0.1)',
|
|
32
|
+
fontSize: '12px',
|
|
33
|
+
color: '#888',
|
|
34
|
+
}}
|
|
35
|
+
suppressHydrationWarning
|
|
36
|
+
>
|
|
37
|
+
<span>🖼️ Без фото</span>
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const srcValue = typeof rawValue === 'object' ? rawValue.url || rawValue.thumbnailURL : rawValue
|
|
43
|
+
if (!srcValue || typeof srcValue !== 'string') {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const src = srcValue.startsWith('http') || srcValue.startsWith('/') ? srcValue : `/${srcValue}`
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div style={{ display: 'inline-flex', alignItems: 'center' }} suppressHydrationWarning>
|
|
51
|
+
<img
|
|
52
|
+
src={src}
|
|
53
|
+
alt="Preview"
|
|
54
|
+
style={{
|
|
55
|
+
width: '192px',
|
|
56
|
+
minWidth: '192px',
|
|
57
|
+
height: '108px',
|
|
58
|
+
minHeight: '108px',
|
|
59
|
+
display: 'block',
|
|
60
|
+
objectFit: 'cover',
|
|
61
|
+
borderRadius: '6px',
|
|
62
|
+
border: '1px solid rgba(255, 255, 255, 0.25)',
|
|
63
|
+
background: '#111',
|
|
64
|
+
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.4)',
|
|
65
|
+
}}
|
|
66
|
+
onError={() => setHasError(true)}
|
|
67
|
+
suppressHydrationWarning
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default ImageCell
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} MapCellProps
|
|
7
|
+
* @property {any} [cellData]
|
|
8
|
+
* @property {any} [value]
|
|
9
|
+
* @property {any} [rowData]
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Map preview cell for Payload CMS admin tables with OpenStreetMap link.
|
|
14
|
+
* @param {MapCellProps} props
|
|
15
|
+
* @returns {React.JSX.Element}
|
|
16
|
+
*/
|
|
17
|
+
export function MapCell({ rowData }) {
|
|
18
|
+
const lat = rowData?.lat || rowData?.latitude || '50.4501'
|
|
19
|
+
const lon = rowData?.lng || rowData?.longitude || '30.5234'
|
|
20
|
+
const address = rowData?.address || rowData?.title || 'Відділення'
|
|
21
|
+
|
|
22
|
+
const zoom = 15
|
|
23
|
+
const latNum = parseFloat(String(lat)) || 50.4501
|
|
24
|
+
const lonNum = parseFloat(String(lon)) || 30.5234
|
|
25
|
+
|
|
26
|
+
const x = Math.floor(((lonNum + 180) / 360) * Math.pow(2, zoom))
|
|
27
|
+
const y = Math.floor(
|
|
28
|
+
((1 - Math.log(Math.tan((latNum * Math.PI) / 180) + 1 / Math.cos((latNum * Math.PI) / 180)) / Math.PI) / 2) *
|
|
29
|
+
Math.pow(2, zoom)
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
const osmTileUrl = `https://tile.openstreetmap.org/${zoom}/${x}/${y}.png`
|
|
33
|
+
const osmMapUrl = `https://www.openstreetmap.org/?mlat=${latNum}&mlon=${lonNum}#map=16/${latNum}/${lonNum}`
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<a
|
|
37
|
+
href={osmMapUrl}
|
|
38
|
+
target="_blank"
|
|
39
|
+
rel="noopener noreferrer"
|
|
40
|
+
style={{
|
|
41
|
+
display: 'inline-flex',
|
|
42
|
+
alignItems: 'center',
|
|
43
|
+
gap: '8px',
|
|
44
|
+
textDecoration: 'none',
|
|
45
|
+
color: 'inherit',
|
|
46
|
+
}}
|
|
47
|
+
suppressHydrationWarning
|
|
48
|
+
>
|
|
49
|
+
<img
|
|
50
|
+
src={osmTileUrl}
|
|
51
|
+
alt="OpenStreetMap"
|
|
52
|
+
style={{
|
|
53
|
+
width: '100px',
|
|
54
|
+
height: '56px',
|
|
55
|
+
objectFit: 'cover',
|
|
56
|
+
borderRadius: '4px',
|
|
57
|
+
border: '1px solid rgba(255, 255, 255, 0.2)',
|
|
58
|
+
background: '#222',
|
|
59
|
+
}}
|
|
60
|
+
onError={(e) => {
|
|
61
|
+
/** @type {HTMLElement} */ (e.target).style.display = 'none'
|
|
62
|
+
}}
|
|
63
|
+
suppressHydrationWarning
|
|
64
|
+
/>
|
|
65
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '2px' }}>
|
|
66
|
+
<span style={{ fontSize: '12px', fontWeight: 600 }}>📍 {address}</span>
|
|
67
|
+
<span style={{ fontSize: '10px', color: '#1471d1' }}>🗺️ OpenStreetMap ({latNum.toFixed(4)}, {lonNum.toFixed(4)})</span>
|
|
68
|
+
</div>
|
|
69
|
+
</a>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default MapCell
|