@nan0web/ui-payload 3.4.0 → 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.4.0",
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