diff --git a/src/components/common/IconTile.tsx b/src/components/common/IconTile.tsx
index 5c6e77b..6202c0f 100644
--- a/src/components/common/IconTile.tsx
+++ b/src/components/common/IconTile.tsx
@@ -46,7 +46,9 @@ export function IconTile({
const box = size === 'sm' ? 'h-6 w-6 rounded-md' : size === 'lg' ? 'h-10 w-10 rounded-xl' : 'h-7 w-7 rounded-lg';
const glyph = size === 'sm' ? 'h-3.5 w-3.5' : size === 'lg' ? 'h-5 w-5' : 'h-4 w-4';
return (
-
+
{createElement(iconComponent(name), { className: glyph, strokeWidth: 2, 'aria-hidden': true })}
);
diff --git a/src/components/common/Logo.tsx b/src/components/common/Logo.tsx
index d6fccb5..bd9c080 100644
--- a/src/components/common/Logo.tsx
+++ b/src/components/common/Logo.tsx
@@ -22,12 +22,36 @@ export function DefaultLogo() {
className="h-7 w-auto max-w-[320px]"
>
-
-
-
-
-
-
+
+
+
+
+
+
);
}
diff --git a/src/components/forms/DynamicForm.tsx b/src/components/forms/DynamicForm.tsx
index b8c8908..08c505b 100644
--- a/src/components/forms/DynamicForm.tsx
+++ b/src/components/forms/DynamicForm.tsx
@@ -7,6 +7,7 @@
import { humanize } from '@/lib/humanize';
import { PageHeader } from '@/components/common/PageHeader';
+import { HelpPanel } from '@/help/HelpPanel';
import { iconForView } from '@/lib/viewIcon';
import { useState, useEffect, useCallback, useMemo } from 'react';
import { flushSync } from 'react-dom';
@@ -124,6 +125,15 @@ export function DynamicForm({ viewName, objectId }: DynamicFormProps) {
return { ...fields, properties: filtered };
}, [resolved, selectedVariant, schema]);
+ // INBUXA: whose fields these are, for their help ids: the variant's schema
+ // (x:UserAccount) when the object has variants, else the object (x:Domain).
+ const helpScope = useMemo(() => {
+ if (!resolved) return undefined;
+ const { sch, obj } = resolved;
+ if (sch.type === 'single') return obj.objectName;
+ return sch.variants.find((v) => v.name === selectedVariant)?.schemaName ?? obj.objectName;
+ }, [resolved, selectedVariant]);
+
const currentForm = useMemo((): Form | null => {
if (!schema || !resolved) return null;
const { obj, sch } = resolved;
@@ -761,6 +771,7 @@ export function DynamicForm({ viewName, objectId }: DynamicFormProps) {
icon={iconForView(schema, viewName)}
title={formTitle}
subtitle={formSubtitle}
+ actions={}
/>
{generalError && (
@@ -834,6 +845,7 @@ export function DynamicForm({ viewName, objectId }: DynamicFormProps) {
sieveScriptName={
isSieveScriptField(resolved.obj.objectName, formField.name) ? scriptName : undefined
}
+ helpScope={helpScope}
/>
);
diff --git a/src/components/forms/FieldWidget.tsx b/src/components/forms/FieldWidget.tsx
index 3931146..ce62d3f 100644
--- a/src/components/forms/FieldWidget.tsx
+++ b/src/components/forms/FieldWidget.tsx
@@ -9,7 +9,8 @@ import { humanize } from '@/lib/humanize';
import { useState, useEffect, useMemo, type KeyboardEvent } from 'react';
import { useTranslation } from 'react-i18next';
import { useBufferedValue, useResetOnChange } from '@/hooks/useBufferedValue';
-import ReactMarkdown from 'react-markdown';
+import { HelpTip } from '@/help/HelpTip';
+import { fieldHelp } from '@/help/texts';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Button } from '@/components/ui/button';
@@ -63,6 +64,8 @@ export interface FieldWidgetProps {
error?: string;
schema: Schema;
sieveScriptName?: string;
+ /** INBUXA: the object or schema that owns this field, for its help id (`scope.field`). */
+ helpScope?: string;
}
function getRequiredMarker(field: Field, readOnly: boolean): 'required' | 'optional' | null {
@@ -83,7 +86,8 @@ function getRequiredMarker(field: Field, readOnly: boolean): 'required' | 'optio
export function FieldWidget(props: FieldWidgetProps) {
const { t } = useTranslation();
- const { field, formField, value, onChange, readOnly, error, schema, sieveScriptName } = props;
+ const { field, formField, value, onChange, readOnly, error, schema, sieveScriptName, helpScope } = props;
+ const helpId = helpScope ? `${helpScope}.${formField.name}` : undefined;
const ft = field.type;
const edition = useEffectiveEdition();
@@ -233,12 +237,8 @@ export function FieldWidget(props: FieldWidgetProps) {
)}
+
- {field.description && (
-
- {field.description.replace(/\\n/g, '\n')}
-
- )}
{widget}
{sieveScriptName !== undefined && ft.type === 'string' && (
@@ -1409,6 +1409,7 @@ function EmbeddedObjectField({
if (resolvedSchema.type === 'single') {
const fields = resolvedSchema.fields;
+ const helpScopeHere = resolvedSchema.schemaName ?? objectName;
const form = resolveVariantForm(schema, objectName, objectName, resolvedSchema.schemaName);
const formFields = form?.sections.flatMap((s) => s.fields) ?? [];
@@ -1427,6 +1428,7 @@ function EmbeddedObjectField({
onChange={(v) => handleFieldChange(ff.name, v)}
readOnly={readOnly}
schema={schema}
+ helpScope={helpScopeHere}
/>
);
})}
@@ -1442,6 +1444,7 @@ function EmbeddedObjectField({
onChange={(v) => handleFieldChange(name, v)}
readOnly={readOnly}
schema={schema}
+ helpScope={helpScopeHere}
/>
))}
@@ -1451,6 +1454,7 @@ function EmbeddedObjectField({
const currentType = (objValue['@type'] as string) ?? resolvedSchema.variants[0]?.name ?? '';
const currentVariant = resolvedSchema.variants.find((v) => v.name === currentType);
const variantFields = currentVariant?.fields;
+ const helpScopeHere = currentVariant?.schemaName ?? objectName;
const variantForm = resolveVariantForm(schema, objectName, objectName, currentVariant?.schemaName);
const variantFormFields = variantForm?.sections.flatMap((s) => s.fields) ?? [];
@@ -1491,6 +1495,7 @@ function EmbeddedObjectField({
onChange={(v) => handleFieldChange(ff.name, v)}
readOnly={readOnly}
schema={schema}
+ helpScope={helpScopeHere}
/>
);
})}
@@ -1507,6 +1512,7 @@ function EmbeddedObjectField({
onChange={(v) => handleFieldChange(name, v)}
readOnly={readOnly}
schema={schema}
+ helpScope={helpScopeHere}
/>
))}
diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx
index 08df8ff..4346dbc 100644
--- a/src/components/layout/Sidebar.tsx
+++ b/src/components/layout/Sidebar.tsx
@@ -427,9 +427,9 @@ export function Sidebar() {
const layout: Layout | undefined = layouts.find((l) => l.name === activeSection);
if (!layout) return null;
-
// Folding to a rail is for wide screens; a phone keeps the slide-over.
- const collapsed = sidebarCollapsed && typeof window !== 'undefined' && window.matchMedia('(min-width: 768px)').matches;
+ const collapsed =
+ sidebarCollapsed && typeof window !== 'undefined' && window.matchMedia('(min-width: 768px)').matches;
if (collapsed) {
return (
@@ -476,7 +476,9 @@ export function Sidebar() {
/>