@byline/admin 2.5.2 → 2.6.0
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/dist/fields/array/array-field.d.ts +14 -0
- package/dist/fields/array/array-field.js +177 -0
- package/dist/fields/array/array-field.module.js +11 -0
- package/dist/fields/array/array-field_module.css +32 -0
- package/dist/fields/blocks/blocks-field.d.ts +13 -0
- package/dist/fields/blocks/blocks-field.js +245 -0
- package/dist/fields/blocks/blocks-field.module.js +26 -0
- package/dist/fields/blocks/blocks-field_module.css +107 -0
- package/dist/fields/checkbox/checkbox-field.d.ts +16 -0
- package/dist/fields/checkbox/checkbox-field.js +28 -0
- package/dist/fields/checkbox/checkbox-field.module.js +6 -0
- package/dist/fields/checkbox/checkbox-field_module.css +4 -0
- package/dist/fields/column-formatter.d.ts +20 -0
- package/dist/fields/column-formatter.js +15 -0
- package/dist/fields/date-time-formatter.d.ts +16 -0
- package/dist/fields/date-time-formatter.js +8 -0
- package/dist/fields/datetime/datetime-field.d.ts +16 -0
- package/dist/fields/datetime/datetime-field.js +37 -0
- package/dist/fields/datetime/datetime-field.module.js +5 -0
- package/dist/fields/datetime/datetime-field_module.css +4 -0
- package/dist/fields/draggable-context-menu.d.ts +6 -0
- package/dist/fields/draggable-context-menu.js +85 -0
- package/dist/fields/draggable-context-menu.module.js +15 -0
- package/dist/fields/draggable-context-menu_module.css +91 -0
- package/dist/fields/field-helpers.d.ts +26 -0
- package/dist/fields/field-helpers.js +50 -0
- package/dist/fields/field-renderer.d.ts +37 -0
- package/dist/fields/field-renderer.js +206 -0
- package/dist/fields/field-renderer.module.js +8 -0
- package/dist/fields/field-renderer_module.css +11 -0
- package/dist/fields/field-services-context.d.ts +16 -0
- package/dist/fields/field-services-context.js +13 -0
- package/dist/fields/field-services-types.d.ts +63 -0
- package/dist/fields/field-services-types.js +1 -0
- package/dist/fields/file/file-field.d.ts +19 -0
- package/dist/fields/file/file-field.js +225 -0
- package/dist/fields/file/file-field.module.js +18 -0
- package/dist/fields/file/file-field_module.css +131 -0
- package/dist/fields/file/file-upload-field.d.ts +21 -0
- package/dist/fields/file/file-upload-field.js +130 -0
- package/dist/fields/file/file-upload-field.module.js +15 -0
- package/dist/fields/file/file-upload-field_module.css +74 -0
- package/dist/fields/group/group-field.d.ts +15 -0
- package/dist/fields/group/group-field.js +59 -0
- package/dist/fields/group/group-field.module.js +9 -0
- package/dist/fields/group/group-field_module.css +27 -0
- package/dist/fields/image/image-field.d.ts +19 -0
- package/dist/fields/image/image-field.js +241 -0
- package/dist/fields/image/image-field.module.js +22 -0
- package/dist/fields/image/image-field_module.css +121 -0
- package/dist/fields/image/image-upload-field.d.ts +21 -0
- package/dist/fields/image/image-upload-field.js +190 -0
- package/dist/fields/image/image-upload-field.module.js +19 -0
- package/dist/fields/image/image-upload-field_module.css +92 -0
- package/dist/fields/local-date-time.d.ts +27 -0
- package/dist/fields/local-date-time.js +49 -0
- package/dist/fields/locale-badge.d.ts +18 -0
- package/dist/fields/locale-badge.js +10 -0
- package/dist/fields/locale-badge.module.js +5 -0
- package/dist/fields/locale-badge_module.css +27 -0
- package/dist/fields/numerical/numerical-field.d.ts +18 -0
- package/dist/fields/numerical/numerical-field.js +74 -0
- package/dist/fields/relation/relation-display.d.ts +40 -0
- package/dist/fields/relation/relation-display.js +58 -0
- package/dist/fields/relation/relation-display.module.js +9 -0
- package/dist/fields/relation/relation-display_module.css +21 -0
- package/dist/fields/relation/relation-field.d.ts +18 -0
- package/dist/fields/relation/relation-field.js +138 -0
- package/dist/fields/relation/relation-field.module.js +13 -0
- package/dist/fields/relation/relation-field_module.css +62 -0
- package/dist/fields/relation/relation-picker.d.ts +49 -0
- package/dist/fields/relation/relation-picker.js +236 -0
- package/dist/fields/relation/relation-picker.module.js +26 -0
- package/dist/fields/relation/relation-picker_module.css +124 -0
- package/dist/fields/relation/relation-summary.d.ts +31 -0
- package/dist/fields/relation/relation-summary.js +50 -0
- package/dist/fields/relation/relation-summary.module.js +11 -0
- package/dist/fields/relation/relation-summary_module.css +37 -0
- package/dist/fields/select/select-field.d.ts +16 -0
- package/dist/fields/select/select-field.js +50 -0
- package/dist/fields/select/select-field.module.js +5 -0
- package/dist/fields/select/select-field_module.css +4 -0
- package/dist/fields/sortable-item.d.ts +15 -0
- package/dist/fields/sortable-item.js +81 -0
- package/dist/fields/sortable-item.module.js +22 -0
- package/dist/fields/sortable-item_module.css +124 -0
- package/dist/fields/text/text-field.d.ts +20 -0
- package/dist/fields/text/text-field.js +104 -0
- package/dist/fields/text/text-field.module.js +6 -0
- package/dist/fields/text/text-field_module.css +5 -0
- package/dist/fields/text-area/text-area-field.d.ts +20 -0
- package/dist/fields/text-area/text-area-field.js +105 -0
- package/dist/fields/text-area/text-area-field.module.js +6 -0
- package/dist/fields/text-area/text-area-field_module.css +5 -0
- package/dist/fields/use-field-change-handler.d.ts +23 -0
- package/dist/fields/use-field-change-handler.js +52 -0
- package/dist/forms/document-actions.d.ts +48 -0
- package/dist/forms/document-actions.js +475 -0
- package/dist/forms/document-actions.module.js +34 -0
- package/dist/forms/document-actions_module.css +118 -0
- package/dist/forms/form-context.d.ts +89 -0
- package/dist/forms/form-context.js +466 -0
- package/dist/forms/form-renderer.d.ts +98 -0
- package/dist/forms/form-renderer.js +597 -0
- package/dist/forms/form-renderer.module.js +46 -0
- package/dist/forms/form-renderer_module.css +245 -0
- package/dist/forms/navigation-guard.d.ts +54 -0
- package/dist/forms/navigation-guard.js +22 -0
- package/dist/forms/path-widget.d.ts +36 -0
- package/dist/forms/path-widget.js +116 -0
- package/dist/forms/path-widget.module.js +8 -0
- package/dist/forms/path-widget_module.css +29 -0
- package/dist/forms/upload-executor.d.ts +57 -0
- package/dist/forms/upload-executor.js +94 -0
- package/dist/lib/translate-validation-error.d.ts +36 -0
- package/dist/lib/translate-validation-error.js +11 -0
- package/dist/modules/admin-account/commands.d.ts +2 -1
- package/dist/modules/admin-account/commands.js +13 -2
- package/dist/modules/admin-account/components/change-password.js +45 -36
- package/dist/modules/admin-account/components/container.js +185 -134
- package/dist/modules/admin-account/components/preferences.d.ts +8 -0
- package/dist/modules/admin-account/components/preferences.js +152 -0
- package/dist/modules/admin-account/components/preferences.module.js +11 -0
- package/dist/modules/admin-account/components/preferences_module.css +41 -0
- package/dist/modules/admin-account/components/update.js +50 -31
- package/dist/modules/admin-account/index.d.ts +3 -3
- package/dist/modules/admin-account/index.js +2 -2
- package/dist/modules/admin-account/schemas.d.ts +4 -0
- package/dist/modules/admin-account/schemas.js +4 -1
- package/dist/modules/admin-account/service.d.ts +1 -0
- package/dist/modules/admin-account/service.js +8 -0
- package/dist/modules/admin-permissions/components/inspector.js +31 -41
- package/dist/modules/admin-roles/components/create.js +43 -26
- package/dist/modules/admin-roles/components/permissions.js +26 -35
- package/dist/modules/admin-roles/components/update.js +26 -16
- package/dist/modules/admin-users/components/create.js +60 -40
- package/dist/modules/admin-users/components/roles.js +9 -15
- package/dist/modules/admin-users/components/set-password.js +30 -31
- package/dist/modules/admin-users/components/update.js +58 -39
- package/dist/modules/admin-users/dto.js +1 -0
- package/dist/modules/admin-users/repository.d.ts +17 -0
- package/dist/modules/admin-users/schemas.d.ts +4 -0
- package/dist/modules/admin-users/schemas.js +6 -2
- package/dist/modules/auth/components/sign-in-form.js +10 -8
- package/dist/presentation/group.d.ts +27 -0
- package/dist/presentation/group.js +14 -0
- package/dist/presentation/group.module.js +6 -0
- package/dist/presentation/group_module.css +19 -0
- package/dist/presentation/row.d.ts +25 -0
- package/dist/presentation/row.js +8 -0
- package/dist/presentation/row.module.js +5 -0
- package/dist/presentation/row_module.css +18 -0
- package/dist/presentation/tabs.d.ts +25 -0
- package/dist/presentation/tabs.js +39 -0
- package/dist/presentation/tabs.module.js +10 -0
- package/dist/presentation/tabs_module.css +68 -0
- package/dist/react.d.ts +66 -0
- package/dist/react.js +36 -0
- package/dist/services/admin-services-types.d.ts +16 -0
- package/dist/widgets/diff-viewer/diff-modal.d.ts +22 -0
- package/dist/widgets/diff-viewer/diff-modal.js +149 -0
- package/dist/widgets/diff-viewer/diff-modal.module.js +14 -0
- package/dist/widgets/diff-viewer/diff-modal_module.css +56 -0
- package/dist/widgets/status-badge/status-badge.d.ts +25 -0
- package/dist/widgets/status-badge/status-badge.js +37 -0
- package/dist/widgets/status-badge/status-badge.module.js +7 -0
- package/dist/widgets/status-badge/status-badge_module.css +20 -0
- package/package.json +14 -4
- package/src/fields/array/array-field.module.css +48 -0
- package/src/fields/array/array-field.tsx +267 -0
- package/src/fields/blocks/blocks-field.module.css +148 -0
- package/src/fields/blocks/blocks-field.tsx +323 -0
- package/src/fields/checkbox/checkbox-field.module.css +4 -0
- package/src/fields/checkbox/checkbox-field.tsx +54 -0
- package/src/fields/column-formatter.tsx +31 -0
- package/src/fields/date-time-formatter.tsx +22 -0
- package/src/fields/datetime/datetime-field.module.css +13 -0
- package/src/fields/datetime/datetime-field.tsx +54 -0
- package/src/fields/draggable-context-menu.module.css +127 -0
- package/src/fields/draggable-context-menu.tsx +87 -0
- package/src/fields/field-helpers.ts +69 -0
- package/src/fields/field-renderer.module.css +22 -0
- package/src/fields/field-renderer.tsx +288 -0
- package/src/fields/field-services-context.tsx +35 -0
- package/src/fields/field-services-types.ts +68 -0
- package/src/fields/file/file-field.module.css +153 -0
- package/src/fields/file/file-field.tsx +286 -0
- package/src/fields/file/file-upload-field.module.css +101 -0
- package/src/fields/file/file-upload-field.tsx +187 -0
- package/src/fields/group/group-field.module.css +43 -0
- package/src/fields/group/group-field.tsx +84 -0
- package/src/fields/image/image-field.module.css +155 -0
- package/src/fields/image/image-field.tsx +306 -0
- package/src/fields/image/image-upload-field.module.css +123 -0
- package/src/fields/image/image-upload-field.tsx +276 -0
- package/src/fields/local-date-time.tsx +88 -0
- package/src/fields/locale-badge.module.css +37 -0
- package/src/fields/locale-badge.tsx +32 -0
- package/src/fields/numerical/numerical-field.tsx +114 -0
- package/src/fields/relation/relation-display.module.css +36 -0
- package/src/fields/relation/relation-display.tsx +130 -0
- package/src/fields/relation/relation-field.module.css +83 -0
- package/src/fields/relation/relation-field.tsx +211 -0
- package/src/fields/relation/relation-picker.module.css +168 -0
- package/src/fields/relation/relation-picker.tsx +326 -0
- package/src/fields/relation/relation-summary.module.css +55 -0
- package/src/fields/relation/relation-summary.tsx +123 -0
- package/src/fields/select/select-field.module.css +13 -0
- package/src/fields/select/select-field.tsx +61 -0
- package/src/fields/sortable-item.module.css +167 -0
- package/src/fields/sortable-item.tsx +106 -0
- package/src/fields/text/text-field.module.css +13 -0
- package/src/fields/text/text-field.tsx +146 -0
- package/src/fields/text-area/text-area-field.module.css +13 -0
- package/src/fields/text-area/text-area-field.tsx +147 -0
- package/src/fields/use-field-change-handler.ts +112 -0
- package/src/forms/document-actions.module.css +160 -0
- package/src/forms/document-actions.tsx +482 -0
- package/src/forms/form-context.tsx +704 -0
- package/src/forms/form-renderer.module.css +321 -0
- package/src/forms/form-renderer.tsx +891 -0
- package/src/forms/navigation-guard.tsx +98 -0
- package/src/forms/path-widget.module.css +41 -0
- package/src/forms/path-widget.test.tsx +217 -0
- package/src/forms/path-widget.tsx +183 -0
- package/src/forms/upload-executor.ts +192 -0
- package/src/lib/translate-validation-error.ts +56 -0
- package/src/modules/admin-account/commands.ts +13 -0
- package/src/modules/admin-account/components/change-password.tsx +46 -31
- package/src/modules/admin-account/components/container.tsx +83 -38
- package/src/modules/admin-account/components/preferences.module.css +60 -0
- package/src/modules/admin-account/components/preferences.tsx +203 -0
- package/src/modules/admin-account/components/update.tsx +53 -27
- package/src/modules/admin-account/index.ts +3 -0
- package/src/modules/admin-account/schemas.ts +13 -0
- package/src/modules/admin-account/service.ts +12 -0
- package/src/modules/admin-permissions/components/inspector.tsx +22 -14
- package/src/modules/admin-roles/components/create.tsx +51 -23
- package/src/modules/admin-roles/components/permissions.tsx +25 -21
- package/src/modules/admin-roles/components/update.tsx +37 -19
- package/src/modules/admin-users/components/create.tsx +63 -34
- package/src/modules/admin-users/components/roles.tsx +9 -8
- package/src/modules/admin-users/components/set-password.tsx +34 -28
- package/src/modules/admin-users/components/update.tsx +58 -36
- package/src/modules/admin-users/dto.ts +1 -0
- package/src/modules/admin-users/repository.ts +17 -0
- package/src/modules/admin-users/schemas.ts +12 -0
- package/src/modules/auth/components/sign-in-form.tsx +14 -8
- package/src/presentation/group.module.css +41 -0
- package/src/presentation/group.tsx +40 -0
- package/src/presentation/row.module.css +32 -0
- package/src/presentation/row.tsx +33 -0
- package/src/presentation/tabs.module.css +107 -0
- package/src/presentation/tabs.tsx +84 -0
- package/src/react.ts +84 -0
- package/src/services/admin-services-types.ts +18 -0
- package/src/widgets/diff-viewer/diff-modal.module.css +79 -0
- package/src/widgets/diff-viewer/diff-modal.tsx +186 -0
- package/src/widgets/status-badge/status-badge.module.css +31 -0
- package/src/widgets/status-badge/status-badge.tsx +71 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
// undefined will use the user's locale
|
|
6
|
+
const dateTimeFormatter = new Intl.DateTimeFormat(undefined, {
|
|
7
|
+
year: 'numeric',
|
|
8
|
+
month: 'short', // <- short month text (locale-aware)
|
|
9
|
+
day: '2-digit',
|
|
10
|
+
hour: 'numeric',
|
|
11
|
+
minute: 'numeric',
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const dateFormatter = new Intl.DateTimeFormat(undefined, {
|
|
15
|
+
year: 'numeric',
|
|
16
|
+
month: 'short', // <- short month text (locale-aware)
|
|
17
|
+
day: '2-digit',
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
interface LocalDateTimeProps {
|
|
21
|
+
value: string | Date | null | undefined
|
|
22
|
+
mode?: 'datetime' | 'date'
|
|
23
|
+
className?: string
|
|
24
|
+
fallback?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Client-side date/time formatter that displays dates in the user's local timezone.
|
|
29
|
+
*
|
|
30
|
+
* This component avoids hydration mismatches by:
|
|
31
|
+
* 1. Rendering a placeholder on the server/initial render
|
|
32
|
+
* 2. Updating to the correctly formatted date after hydration
|
|
33
|
+
*
|
|
34
|
+
* Use this instead of formatDateTime when you need to display dates
|
|
35
|
+
* in the user's local timezone rather than UTC.
|
|
36
|
+
*/
|
|
37
|
+
export function LocalDateTime({
|
|
38
|
+
value,
|
|
39
|
+
mode = 'datetime',
|
|
40
|
+
className,
|
|
41
|
+
fallback = '—',
|
|
42
|
+
}: LocalDateTimeProps): React.JSX.Element {
|
|
43
|
+
const [formattedDate, setFormattedDate] = useState<string | null>(null)
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (value == null) {
|
|
47
|
+
setFormattedDate(null)
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const date = typeof value === 'string' ? Date.parse(value) : value.getTime()
|
|
52
|
+
if (Number.isNaN(date)) {
|
|
53
|
+
setFormattedDate('Error')
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const formatter = mode === 'datetime' ? dateTimeFormatter : dateFormatter
|
|
58
|
+
setFormattedDate(formatter.format(date))
|
|
59
|
+
}, [value, mode])
|
|
60
|
+
|
|
61
|
+
// Show fallback during SSR and before useEffect runs
|
|
62
|
+
if (formattedDate === null) {
|
|
63
|
+
return <span className={className}>{value ? '...' : fallback}</span>
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return <span className={className}>{formattedDate}</span>
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Formats a date string to the user's local timezone.
|
|
71
|
+
*
|
|
72
|
+
* WARNING: Only use this in client components where hydration
|
|
73
|
+
* mismatches are acceptable or managed. For most cases, prefer
|
|
74
|
+
* the <LocalDateTime /> component.
|
|
75
|
+
*/
|
|
76
|
+
export function formatLocalDateTime(value: string | Date | null | undefined): string {
|
|
77
|
+
if (value == null) return '—'
|
|
78
|
+
const date = typeof value === 'string' ? Date.parse(value) : value.getTime()
|
|
79
|
+
if (Number.isNaN(date)) return 'Error'
|
|
80
|
+
return dateTimeFormatter.format(date)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function formatLocalDate(value: string | Date | null | undefined): string {
|
|
84
|
+
if (value == null) return '—'
|
|
85
|
+
const date = typeof value === 'string' ? Date.parse(value) : value.getTime()
|
|
86
|
+
if (Number.isNaN(date)) return 'Error'
|
|
87
|
+
return dateFormatter.format(date)
|
|
88
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocaleBadge — small inline badge shown next to a localised field label.
|
|
3
|
+
*
|
|
4
|
+
* Override handle: `.byline-locale-badge`
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
.badge,
|
|
8
|
+
:global(.byline-locale-badge) {
|
|
9
|
+
display: inline-flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
gap: 0.125rem;
|
|
12
|
+
margin-left: 0.375rem;
|
|
13
|
+
padding: 0.125rem 0.375rem;
|
|
14
|
+
border: var(--border-width-thin) var(--border-style-solid) var(--yellow-400);
|
|
15
|
+
border-radius: var(--border-radius-sm);
|
|
16
|
+
background-color: var(--yellow-100);
|
|
17
|
+
color: var(--yellow-800);
|
|
18
|
+
font-size: 0.6rem;
|
|
19
|
+
font-weight: var(--font-weight-semibold);
|
|
20
|
+
line-height: 1;
|
|
21
|
+
letter-spacing: var(--letter-spacing-wide);
|
|
22
|
+
text-transform: uppercase;
|
|
23
|
+
pointer-events: none;
|
|
24
|
+
user-select: none;
|
|
25
|
+
vertical-align: middle;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* ─── Dark theme variants ───────────────────────────────────── */
|
|
29
|
+
|
|
30
|
+
:is([data-theme="dark"], :global(.dark)) {
|
|
31
|
+
.badge,
|
|
32
|
+
:global(.byline-locale-badge) {
|
|
33
|
+
border-color: var(--yellow-700);
|
|
34
|
+
background-color: oklch(from var(--yellow-900) l c h / 0.4);
|
|
35
|
+
color: var(--yellow-400);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import cx from 'classnames'
|
|
10
|
+
|
|
11
|
+
import styles from './locale-badge.module.css'
|
|
12
|
+
|
|
13
|
+
export interface LocaleBadgeProps {
|
|
14
|
+
locale: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Small inline badge shown next to the label of a field that has
|
|
19
|
+
* `localized: true` in its schema definition. Indicates which locale
|
|
20
|
+
* the editor is currently working in.
|
|
21
|
+
*
|
|
22
|
+
* Stable override handle: `.byline-locale-badge`.
|
|
23
|
+
*/
|
|
24
|
+
export const LocaleBadge = ({ locale }: LocaleBadgeProps) => (
|
|
25
|
+
<span
|
|
26
|
+
aria-hidden="true"
|
|
27
|
+
title={`Localised — editing ${locale.toUpperCase()} content`}
|
|
28
|
+
className={cx('byline-locale-badge', styles.badge)}
|
|
29
|
+
>
|
|
30
|
+
{locale}
|
|
31
|
+
</span>
|
|
32
|
+
)
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
CounterField,
|
|
11
|
+
DecimalField,
|
|
12
|
+
Field,
|
|
13
|
+
FieldComponentSlots,
|
|
14
|
+
FloatField,
|
|
15
|
+
IntegerField,
|
|
16
|
+
} from '@byline/core'
|
|
17
|
+
import { Input } from '@byline/ui/react'
|
|
18
|
+
|
|
19
|
+
import { useFieldError, useFieldValue } from '../../forms/form-context'
|
|
20
|
+
|
|
21
|
+
export const NumericalField = ({
|
|
22
|
+
field,
|
|
23
|
+
value,
|
|
24
|
+
defaultValue,
|
|
25
|
+
onChange,
|
|
26
|
+
id,
|
|
27
|
+
path,
|
|
28
|
+
components,
|
|
29
|
+
}: {
|
|
30
|
+
field: IntegerField | FloatField | DecimalField | CounterField
|
|
31
|
+
value?: string | number | null
|
|
32
|
+
defaultValue?: string | number | null
|
|
33
|
+
onChange?: (value: string) => void
|
|
34
|
+
id?: string
|
|
35
|
+
path?: string
|
|
36
|
+
/** Optional UI component slot overrides from the admin config. */
|
|
37
|
+
components?: FieldComponentSlots
|
|
38
|
+
}) => {
|
|
39
|
+
const fieldPath = path ?? field.name
|
|
40
|
+
const fieldError = useFieldError(fieldPath)
|
|
41
|
+
const fieldValue = useFieldValue<string | number | undefined>(fieldPath)
|
|
42
|
+
const incomingValue = value ?? fieldValue ?? defaultValue ?? ''
|
|
43
|
+
const htmlId = id ?? fieldPath
|
|
44
|
+
const displayValue =
|
|
45
|
+
incomingValue === undefined || incomingValue === null ? '' : String(incomingValue)
|
|
46
|
+
|
|
47
|
+
// Custom component slots (from admin config)
|
|
48
|
+
const slots = components
|
|
49
|
+
const CustomLabel = slots?.Label
|
|
50
|
+
const CustomHelpText = slots?.HelpText
|
|
51
|
+
const CustomField = slots?.Field
|
|
52
|
+
const BeforeField = slots?.beforeField
|
|
53
|
+
const AfterField = slots?.afterField
|
|
54
|
+
|
|
55
|
+
// Shared props available to every slot component
|
|
56
|
+
const slotBaseProps = {
|
|
57
|
+
field: field as Field,
|
|
58
|
+
path: fieldPath,
|
|
59
|
+
value: incomingValue,
|
|
60
|
+
error: fieldError,
|
|
61
|
+
id: htmlId,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const hasCustomLabel = !!CustomLabel
|
|
65
|
+
const suppressInputLabel = hasCustomLabel
|
|
66
|
+
const suppressInputHelpText = !!CustomHelpText
|
|
67
|
+
|
|
68
|
+
// ── Label rendering ──────────────────────────────────────────
|
|
69
|
+
const renderLabel = () => {
|
|
70
|
+
if (hasCustomLabel) {
|
|
71
|
+
return <CustomLabel {...slotBaseProps} label={field.label} required={!field.optional} />
|
|
72
|
+
}
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── Field input rendering ────────────────────────────────────
|
|
77
|
+
const renderInput = () => {
|
|
78
|
+
if (CustomField) {
|
|
79
|
+
return (
|
|
80
|
+
<CustomField
|
|
81
|
+
{...slotBaseProps}
|
|
82
|
+
onChange={(v: any) => onChange?.(v)}
|
|
83
|
+
defaultValue={defaultValue}
|
|
84
|
+
placeholder={field.placeholder}
|
|
85
|
+
/>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
return (
|
|
89
|
+
<Input
|
|
90
|
+
type="number"
|
|
91
|
+
id={htmlId}
|
|
92
|
+
name={field.name}
|
|
93
|
+
label={suppressInputLabel ? undefined : field.label}
|
|
94
|
+
required={!field.optional}
|
|
95
|
+
readOnly={field.readOnly}
|
|
96
|
+
helpText={suppressInputHelpText ? undefined : field.helpText}
|
|
97
|
+
value={displayValue}
|
|
98
|
+
onChange={(e) => onChange?.(e.target.value)}
|
|
99
|
+
error={fieldError != null}
|
|
100
|
+
errorText={fieldError}
|
|
101
|
+
/>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div className={`byline-field-${field.type} ${field.name}`}>
|
|
107
|
+
{renderLabel()}
|
|
108
|
+
{BeforeField && <BeforeField {...slotBaseProps} />}
|
|
109
|
+
{renderInput()}
|
|
110
|
+
{AfterField && <AfterField {...slotBaseProps} />}
|
|
111
|
+
{CustomHelpText && <CustomHelpText {...slotBaseProps} helpText={field.helpText} />}
|
|
112
|
+
</div>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RelationDisplay — shared cell renderer for relation picker rows and the
|
|
3
|
+
* relation-summary selected-value tile.
|
|
4
|
+
*
|
|
5
|
+
* Override handles:
|
|
6
|
+
* .byline-relation-cell — single column cell
|
|
7
|
+
* .byline-relation-cell-center — cell with align: center
|
|
8
|
+
* .byline-relation-cell-right — cell with align: right
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
.cell,
|
|
12
|
+
:global(.byline-relation-cell) {
|
|
13
|
+
min-width: 0;
|
|
14
|
+
color: var(--gray-700);
|
|
15
|
+
font-size: var(--font-size-sm);
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
text-overflow: ellipsis;
|
|
18
|
+
white-space: nowrap;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.cell-center,
|
|
22
|
+
:global(.byline-relation-cell-center) {
|
|
23
|
+
text-align: center;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.cell-right,
|
|
27
|
+
:global(.byline-relation-cell-right) {
|
|
28
|
+
text-align: right;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
:is([data-theme="dark"], :global(.dark)) {
|
|
32
|
+
.cell,
|
|
33
|
+
:global(.byline-relation-cell) {
|
|
34
|
+
color: var(--gray-100);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { CollectionDefinition, ColumnDefinition } from '@byline/core'
|
|
10
|
+
import cx from 'classnames'
|
|
11
|
+
|
|
12
|
+
import styles from './relation-display.module.css'
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Shared render helpers used by both the relation picker modal rows and
|
|
16
|
+
// the relation-summary tile on the edit form. Kept in lock-step so a row
|
|
17
|
+
// rendered in the picker looks identical to the selected tile.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Render a single row cell from a `ColumnDefinition`, reading the value
|
|
22
|
+
* from the document's `fields` (with a fallback to top-level metadata like
|
|
23
|
+
* `status`, `updated_at`, `path`). Honours both formatter shapes — plain
|
|
24
|
+
* function → its return value, `{ component }` → the component is rendered.
|
|
25
|
+
*/
|
|
26
|
+
export function PickerCell({
|
|
27
|
+
column,
|
|
28
|
+
record,
|
|
29
|
+
}: {
|
|
30
|
+
column: ColumnDefinition
|
|
31
|
+
record: Record<string, any>
|
|
32
|
+
}) {
|
|
33
|
+
const name = String(column.fieldName)
|
|
34
|
+
const value = record?.fields?.[name] ?? record?.[name]
|
|
35
|
+
|
|
36
|
+
let content: any
|
|
37
|
+
if (column.formatter) {
|
|
38
|
+
if (typeof column.formatter === 'function') {
|
|
39
|
+
content = column.formatter(value, record)
|
|
40
|
+
} else {
|
|
41
|
+
const Comp = column.formatter.component
|
|
42
|
+
content = <Comp value={value} record={record} />
|
|
43
|
+
}
|
|
44
|
+
} else if (value == null) {
|
|
45
|
+
content = null
|
|
46
|
+
} else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
47
|
+
content = String(value)
|
|
48
|
+
} else {
|
|
49
|
+
content = null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
className={cx(
|
|
55
|
+
'byline-relation-cell',
|
|
56
|
+
styles.cell,
|
|
57
|
+
column.align === 'center' && ['byline-relation-cell-center', styles['cell-center']],
|
|
58
|
+
column.align === 'right' && ['byline-relation-cell-right', styles['cell-right']],
|
|
59
|
+
column.className
|
|
60
|
+
)}
|
|
61
|
+
>
|
|
62
|
+
{content}
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** First top-level `text` field name on a collection, or null. */
|
|
68
|
+
export function resolveFallbackDisplayField(
|
|
69
|
+
def: CollectionDefinition | null | undefined
|
|
70
|
+
): string | null {
|
|
71
|
+
if (!def) return null
|
|
72
|
+
const textField = def.fields.find((f) => f.type === 'text')
|
|
73
|
+
return textField?.name ?? null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Resolve the row's primary label text from the document. */
|
|
77
|
+
export function resolveRowLabel(
|
|
78
|
+
doc: Record<string, any> | null | undefined,
|
|
79
|
+
displayField: string | null
|
|
80
|
+
): string | null {
|
|
81
|
+
if (!doc) return null
|
|
82
|
+
if (displayField) {
|
|
83
|
+
const v = doc.fields?.[displayField]
|
|
84
|
+
if (typeof v === 'string' && v.length > 0) return v
|
|
85
|
+
}
|
|
86
|
+
if (typeof doc.fields?.title === 'string' && doc.fields.title.length > 0) {
|
|
87
|
+
return doc.fields.title as string
|
|
88
|
+
}
|
|
89
|
+
if (typeof doc.path === 'string' && doc.path.length > 0) return doc.path as string
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Build the `fields` projection for the picker listing. Unions:
|
|
95
|
+
* - caller-supplied `displayField`
|
|
96
|
+
* - target schema's `useAsTitle` (always included, even when not visible)
|
|
97
|
+
* - every `fieldName` declared in the admin config's `picker` columns
|
|
98
|
+
* - `title` (metadata fallback for rows with no explicit picker columns)
|
|
99
|
+
*
|
|
100
|
+
* Note that the document `path` is top-level metadata on every list response
|
|
101
|
+
* — it is always returned by `getCollectionDocuments` regardless of the
|
|
102
|
+
* `fields` projection. Callers that need `{ title, path }` for downstream
|
|
103
|
+
* normalisation (e.g. the rich-text link plugin) can rely on:
|
|
104
|
+
* - `record.fields[def.useAsTitle]` for the title
|
|
105
|
+
* - `record.path` for the path
|
|
106
|
+
*
|
|
107
|
+
* Returns `undefined` when no target definition is available, leaving the
|
|
108
|
+
* listing endpoint to decide its own default projection.
|
|
109
|
+
*/
|
|
110
|
+
export function resolveSelectFields(
|
|
111
|
+
def: CollectionDefinition | null | undefined,
|
|
112
|
+
displayField: string | undefined,
|
|
113
|
+
pickerColumns: ColumnDefinition[] | undefined
|
|
114
|
+
): string[] | undefined {
|
|
115
|
+
if (!def) return undefined
|
|
116
|
+
const out = new Set<string>()
|
|
117
|
+
if (displayField) out.add(displayField)
|
|
118
|
+
if (def.useAsTitle) out.add(def.useAsTitle)
|
|
119
|
+
const fallback = resolveFallbackDisplayField(def)
|
|
120
|
+
if (fallback) out.add(fallback)
|
|
121
|
+
if (pickerColumns) {
|
|
122
|
+
for (const col of pickerColumns) {
|
|
123
|
+
const name = String(col.fieldName)
|
|
124
|
+
if (def.fields.some((f) => f.name === name)) out.add(name)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (def.fields.some((f) => f.name === 'title')) out.add('title')
|
|
128
|
+
if (out.size === 0) return undefined
|
|
129
|
+
return Array.from(out)
|
|
130
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RelationField — widget for `type: 'relation'` fields.
|
|
3
|
+
*
|
|
4
|
+
* Override handles:
|
|
5
|
+
* .byline-field-relation — outer wrapper
|
|
6
|
+
* .byline-field-relation-header — label row
|
|
7
|
+
* .byline-field-relation-help — help text below the label
|
|
8
|
+
* .byline-field-relation-error-tile — wrapper shown when target collection is unknown
|
|
9
|
+
* .byline-field-relation-error-text — secondary error message below the title
|
|
10
|
+
* .byline-field-relation-tile — bordered selected-value + actions container
|
|
11
|
+
* .byline-field-relation-actions — top-right icon-button group (edit + remove)
|
|
12
|
+
* .byline-field-relation-mono — monospace `<code>` for field/collection names
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
.header,
|
|
16
|
+
:global(.byline-field-relation-header) {
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: baseline;
|
|
19
|
+
gap: var(--spacing-8);
|
|
20
|
+
margin-bottom: 0.25rem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.help,
|
|
24
|
+
:global(.byline-field-relation-help) {
|
|
25
|
+
margin-bottom: 0.25rem;
|
|
26
|
+
color: var(--gray-400);
|
|
27
|
+
font-size: var(--font-size-xs);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.error-tile,
|
|
31
|
+
:global(.byline-field-relation-error-tile) {
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
gap: 0.25rem;
|
|
35
|
+
margin-top: 0.25rem;
|
|
36
|
+
padding: var(--spacing-8);
|
|
37
|
+
border: var(--border-width-thin) var(--border-style-solid) var(--red-700);
|
|
38
|
+
background-color: oklch(from var(--red-900) l c h / 0.2);
|
|
39
|
+
border-radius: var(--border-radius-md);
|
|
40
|
+
color: var(--red-200);
|
|
41
|
+
font-size: var(--font-size-xs);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.error-text,
|
|
45
|
+
:global(.byline-field-relation-error-text) {
|
|
46
|
+
color: oklch(from var(--red-400) l c h / 0.8);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.tile,
|
|
50
|
+
:global(.byline-field-relation-tile) {
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: flex-start;
|
|
53
|
+
justify-content: space-between;
|
|
54
|
+
gap: var(--spacing-8);
|
|
55
|
+
margin-top: 0.25rem;
|
|
56
|
+
padding: var(--spacing-8);
|
|
57
|
+
border: var(--border-width-thin) var(--border-style-solid) var(--primary-500);
|
|
58
|
+
border-radius: var(--border-radius-md);
|
|
59
|
+
color: var(--gray-200);
|
|
60
|
+
font-size: var(--font-size-xs);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.actions,
|
|
64
|
+
:global(.byline-field-relation-actions) {
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
gap: var(--spacing-4);
|
|
68
|
+
flex-shrink: 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
:global(.byline-field-relation-actions .byline-button) {
|
|
72
|
+
color: var(--gray-900);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
:global(.dark .byline-field-relation-actions .byline-button),
|
|
76
|
+
:global([data-theme="dark"] .byline-field-relation-actions .byline-button) {
|
|
77
|
+
color: var(--gray-200);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.mono,
|
|
81
|
+
:global(.byline-field-relation-mono) {
|
|
82
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
83
|
+
}
|