@open-mercato/ui 0.7.1-develop.7194.1.ab4fc81f82 → 0.7.1-develop.7200.1.8ba2d93d3f

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.
@@ -12,6 +12,7 @@ import {
12
12
  SelectValue
13
13
  } from "../../primitives/select.js";
14
14
  import { Switch } from "../../primitives/switch.js";
15
+ import { SwitchField } from "../../primitives/switch-field.js";
15
16
  import { AttachmentsSection } from "../detail/AttachmentsSection.js";
16
17
  import { SwitchableMarkdownInput } from "../inputs/SwitchableMarkdownInput.js";
17
18
  import { TagsInput } from "../inputs/TagsInput.js";
@@ -278,39 +279,51 @@ function EmailDeliverySection({ compose }) {
278
279
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: compose.t("messages.sendViaEmailForcedPublic", "For public visibility, email delivery is always enabled.") })
279
280
  ] });
280
281
  }
281
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded border px-3 py-2", children: [
282
- /* @__PURE__ */ jsxs("div", { children: [
283
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: compose.t("messages.sendViaEmail", "Also send via email") }),
284
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: compose.t("messages.sendViaEmailHint", "Recipients will receive an email copy with a secure link.") })
285
- ] }),
286
- /* @__PURE__ */ jsx(Switch, { checked: compose.sendViaEmail, onCheckedChange: compose.setSendViaEmail })
287
- ] });
282
+ return /* @__PURE__ */ jsx(
283
+ SwitchField,
284
+ {
285
+ containerClassName: "rounded border px-3 py-2",
286
+ label: compose.t("messages.sendViaEmail", "Also send via email"),
287
+ description: compose.t("messages.sendViaEmailHint", "Recipients will receive an email copy with a secure link."),
288
+ checked: compose.sendViaEmail,
289
+ onCheckedChange: compose.setSendViaEmail
290
+ }
291
+ );
288
292
  }
289
293
  function ReplyForwardOptionsRow({ compose }) {
290
294
  if (compose.variant !== "reply" && compose.variant !== "forward") {
291
295
  return null;
292
296
  }
293
297
  return /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
294
- compose.variant === "forward" ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded border px-3 py-2", children: [
295
- /* @__PURE__ */ jsxs("div", { children: [
296
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: compose.t("messages.includeAttachments", "Include attachments") }),
297
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: compose.t("messages.includeAttachmentsHint", "Carry over attachments from the original message.") })
298
- ] }),
299
- /* @__PURE__ */ jsx(Switch, { checked: compose.includeAttachments, onCheckedChange: compose.setIncludeAttachments })
300
- ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded border px-3 py-2", children: [
301
- /* @__PURE__ */ jsxs("div", { children: [
302
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: compose.t("messages.replyAll", "Reply all") }),
303
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: compose.t("messages.replyAllHint", "Include all original recipients.") })
304
- ] }),
305
- /* @__PURE__ */ jsx(Switch, { checked: compose.replyAll, onCheckedChange: compose.setReplyAll })
306
- ] }),
307
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded border px-3 py-2", children: [
308
- /* @__PURE__ */ jsxs("div", { children: [
309
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: compose.t("messages.sendViaEmail", "Also send via email") }),
310
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: compose.t("messages.sendViaEmailHint", "Recipients will receive an email copy with a secure link.") })
311
- ] }),
312
- /* @__PURE__ */ jsx(Switch, { checked: compose.sendViaEmail, onCheckedChange: compose.setSendViaEmail })
313
- ] })
298
+ compose.variant === "forward" ? /* @__PURE__ */ jsx(
299
+ SwitchField,
300
+ {
301
+ containerClassName: "rounded border px-3 py-2",
302
+ label: compose.t("messages.includeAttachments", "Include attachments"),
303
+ description: compose.t("messages.includeAttachmentsHint", "Carry over attachments from the original message."),
304
+ checked: compose.includeAttachments,
305
+ onCheckedChange: compose.setIncludeAttachments
306
+ }
307
+ ) : /* @__PURE__ */ jsx(
308
+ SwitchField,
309
+ {
310
+ containerClassName: "rounded border px-3 py-2",
311
+ label: compose.t("messages.replyAll", "Reply all"),
312
+ description: compose.t("messages.replyAllHint", "Include all original recipients."),
313
+ checked: compose.replyAll,
314
+ onCheckedChange: compose.setReplyAll
315
+ }
316
+ ),
317
+ /* @__PURE__ */ jsx(
318
+ SwitchField,
319
+ {
320
+ containerClassName: "rounded border px-3 py-2",
321
+ label: compose.t("messages.sendViaEmail", "Also send via email"),
322
+ description: compose.t("messages.sendViaEmailHint", "Recipients will receive an email copy with a secure link."),
323
+ checked: compose.sendViaEmail,
324
+ onCheckedChange: compose.setSendViaEmail
325
+ }
326
+ )
314
327
  ] });
315
328
  }
316
329
  function MessageComposeFormBody({ compose }) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/backend/messages/message-compose-form-groups.tsx"],
4
- "sourcesContent": ["import * as React from 'react'\nimport { FileCode, Globe, Lock } from 'lucide-react'\nimport { type CrudField } from '../CrudForm'\nimport { IconButton } from '../../primitives/icon-button'\nimport { Input } from '../../primitives/input'\nimport { EmailInput } from '../../primitives/email-input'\nimport { Label } from '../../primitives/label'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../../primitives/select'\nimport { Switch } from '../../primitives/switch'\nimport { AttachmentsSection } from '../detail/AttachmentsSection'\nimport { SwitchableMarkdownInput } from '../inputs/SwitchableMarkdownInput'\nimport { TagsInput } from '../inputs/TagsInput'\nimport { MessagePrioritySelector } from './MessagePrioritySelector'\nimport type { UseMessageComposeResult } from './useMessageCompose'\n\ntype ComposeProps = {\n compose: UseMessageComposeResult\n}\n\nfunction RecipientTagsInput({ compose }: ComposeProps) {\n return (\n <TagsInput\n value={compose.recipientIds}\n onChange={compose.setRecipientIds}\n selectedOptions={compose.selectedRecipientOptions}\n resolveLabel={compose.resolveRecipientLabel}\n loadSuggestions={compose.loadRecipientSuggestions}\n placeholder={compose.t('messages.placeholders.recipients', 'Search recipients...')}\n allowCustomValues={false}\n showSuggestionsOnFocus={false}\n />\n )\n}\n\nfunction VisibilitySelector({ compose }: ComposeProps) {\n return (\n <>\n <Label>{compose.t('messages.visibility', 'Visibility')}</Label>\n <div\n className=\"inline-flex items-center gap-1 rounded-md border bg-background p-1\"\n role=\"radiogroup\"\n aria-label={compose.t('messages.visibility', 'Visibility')}\n >\n <IconButton\n type=\"button\"\n size=\"xs\"\n variant={compose.visibility === 'internal' ? 'outline' : 'ghost'}\n role=\"radio\"\n aria-checked={compose.visibility === 'internal'}\n aria-label={compose.t('messages.visibilityInternal', 'Internal')}\n title={compose.t('messages.visibilityInternal', 'Internal')}\n className=\"h-7 w-7\"\n onClick={() => compose.setVisibility('internal')}\n >\n <Lock className=\"h-3.5 w-3.5\" />\n </IconButton>\n <IconButton\n type=\"button\"\n size=\"xs\"\n variant={compose.visibility === 'public' ? 'outline' : 'ghost'}\n role=\"radio\"\n aria-checked={compose.visibility === 'public'}\n aria-label={compose.t('messages.visibilityPublic', 'Public')}\n title={compose.t('messages.visibilityPublic', 'Public')}\n className=\"h-7 w-7\"\n onClick={() => compose.setVisibility('public')}\n >\n <Globe className=\"h-3.5 w-3.5\" />\n </IconButton>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {compose.visibility === 'public'\n ? compose.t('messages.visibilityPublicHint', 'Public messages are sent to external email only.')\n : compose.t('messages.visibilityInternalHint', 'Internal messages are sent to selected system users.')}\n </p>\n </>\n )\n}\n\nfunction ContextActionsSection({ compose }: ComposeProps) {\n if (!compose.shouldShowContextActions) return null\n\n return (\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {compose.normalizedRequiredActionMode === 'optional' ? (\n <div className=\"flex items-center justify-between rounded border px-3 py-2 sm:col-span-2\">\n <div>\n <p className=\"text-sm font-medium\">\n {compose.t('messages.composer.objectPicker.actionRequiredLabel', 'Action required')}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {compose.t('messages.composer.objectPicker.actionRequiredHint', 'Mark this object as requiring recipient action.')}\n </p>\n </div>\n <Switch checked={compose.contextActionRequired} onCheckedChange={compose.setContextActionRequired} />\n </div>\n ) : null}\n {compose.normalizedRequiredActionMode === 'required' || compose.contextActionRequired ? (\n <div className=\"space-y-2 sm:col-span-2\">\n <Label htmlFor=\"messages-compose-context-action-type\">\n {compose.t('messages.composer.objectPicker.actionTypeLabel', 'Action type')}\n </Label>\n <Select\n value={compose.contextActionType || undefined}\n onValueChange={(value) => compose.setContextActionType(value || '')}\n >\n <SelectTrigger id=\"messages-compose-context-action-type\">\n <SelectValue placeholder={compose.t('messages.composer.objectPicker.actionTypePlaceholder', 'Select action')} />\n </SelectTrigger>\n <SelectContent>\n {compose.contextActionOptions.map((option) => (\n <SelectItem key={option.id} value={option.id}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction PrioritySelector({ compose }: ComposeProps) {\n return (\n <>\n <Label>{compose.t('messages.priority', 'Priority')}</Label>\n <MessagePrioritySelector\n value={compose.priority}\n onChange={compose.setPriority}\n t={compose.t}\n />\n </>\n )\n}\n\nfunction MarkdownBodySection({\n compose,\n label,\n placeholder,\n inputId,\n rows,\n textareaClassName,\n}: ComposeProps & {\n label: string\n placeholder: string\n inputId: string\n rows: number\n textareaClassName: string\n}) {\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-center justify-between gap-2\">\n <Label htmlFor={inputId}>{label}</Label>\n <IconButton\n type=\"button\"\n size=\"sm\"\n variant={compose.bodyFormat === 'markdown' ? 'outline' : 'ghost'}\n aria-pressed={compose.bodyFormat === 'markdown'}\n onClick={() => compose.setBodyFormat((previousValue) => (previousValue === 'markdown' ? 'text' : 'markdown'))}\n title={compose.t('messages.bodyFormat.toggle', 'Toggle markdown')}\n >\n <FileCode className=\"h-4 w-4\" />\n </IconButton>\n </div>\n <div id={inputId}>\n <SwitchableMarkdownInput\n value={compose.body}\n onChange={compose.setBody}\n isMarkdownEnabled={compose.bodyFormat === 'markdown'}\n rows={rows}\n placeholder={placeholder}\n textareaClassName={textareaClassName}\n />\n </div>\n </div>\n )\n}\n\nfunction ComposeModeFields({ compose }: ComposeProps) {\n return (\n <>\n <div className=\"space-y-3\">\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"space-y-2\">\n {compose.visibility === 'public' ? (\n <>\n <Label htmlFor=\"messages-compose-external-email\">{compose.t('messages.externalEmail', 'External email')}</Label>\n <EmailInput\n id=\"messages-compose-external-email\"\n value={compose.externalEmail}\n onChange={(event) => compose.setExternalEmail(event.target.value)}\n placeholder={compose.t('messages.placeholders.externalEmail', 'name@example.com')}\n />\n </>\n ) : (\n <>\n <Label htmlFor=\"messages-compose-recipients\">{compose.t('messages.to', 'To')}</Label>\n <RecipientTagsInput compose={compose} />\n </>\n )}\n </div>\n\n <div className=\"space-y-2\">\n <VisibilitySelector compose={compose} />\n </div>\n </div>\n\n <ContextActionsSection compose={compose} />\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"messages-compose-subject\">{compose.t('messages.subject', 'Subject')}</Label>\n <Input\n id=\"messages-compose-subject\"\n value={compose.subject}\n onChange={(event) => compose.setSubject(event.target.value)}\n placeholder={compose.t('messages.placeholders.subject', 'Enter subject...')}\n />\n </div>\n\n <div className=\"space-y-2\">\n <PrioritySelector compose={compose} />\n </div>\n </div>\n\n <MarkdownBodySection\n compose={compose}\n label={compose.t('messages.body', 'Message')}\n placeholder={compose.t('messages.placeholders.body', 'Write your message...')}\n inputId=\"messages-compose-body\"\n rows={8}\n textareaClassName=\"min-h-[180px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n\n <div className=\"space-y-2\">\n <Label>{compose.t('messages.attachedFiles', 'Attachments')}</Label>\n <AttachmentsSection\n entityId={compose.attachmentEntityId}\n recordId={compose.attachmentRecordId}\n showHeader={false}\n compact\n onChanged={() => {\n void compose.loadAttachmentIds().catch(() => null)\n }}\n />\n </div>\n </>\n )\n}\n\nfunction ReplyModeFields({ compose }: ComposeProps) {\n return (\n <>\n <MarkdownBodySection\n compose={compose}\n label={compose.t('messages.replyBody', 'Reply')}\n placeholder={compose.t('messages.placeholders.replyBody', 'Write your reply...')}\n inputId=\"messages-compose-body\"\n rows={8}\n textareaClassName=\"min-h-[180px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n\n <div className=\"space-y-2\">\n <Label>{compose.t('messages.attachedFiles', 'Attachments')}</Label>\n <AttachmentsSection\n entityId={compose.attachmentEntityId}\n recordId={compose.attachmentRecordId}\n showHeader={false}\n compact\n onChanged={() => {\n void compose.loadAttachmentIds().catch(() => null)\n }}\n />\n </div>\n </>\n )\n}\n\nfunction ForwardModeFields({ compose }: ComposeProps) {\n return (\n <>\n <div className=\"space-y-2\">\n <Label htmlFor=\"messages-compose-recipients\">{compose.t('messages.to', 'To')}</Label>\n <RecipientTagsInput compose={compose} />\n </div>\n\n <MarkdownBodySection\n compose={compose}\n label={compose.t('messages.forwardContent', 'Forwarded content')}\n placeholder={compose.t('messages.placeholders.forwardContent', 'Review and edit forwarded content...')}\n inputId=\"messages-forward-note\"\n rows={6}\n textareaClassName=\"min-h-[140px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n </>\n )\n}\n\nfunction EmailDeliverySection({ compose }: ComposeProps) {\n if (compose.variant === 'reply' || compose.variant === 'forward') {\n return null\n }\n\n if (compose.isComposePublicVisibility) {\n return (\n <div className=\"rounded border px-3 py-2\">\n <p className=\"text-sm font-medium\">{compose.t('messages.sendViaEmail', 'Also send via email')}</p>\n <p className=\"text-xs text-muted-foreground\">{compose.t('messages.sendViaEmailForcedPublic', 'For public visibility, email delivery is always enabled.')}</p>\n </div>\n )\n }\n\n return (\n <div className=\"flex items-center justify-between rounded border px-3 py-2\">\n <div>\n <p className=\"text-sm font-medium\">{compose.t('messages.sendViaEmail', 'Also send via email')}</p>\n <p className=\"text-xs text-muted-foreground\">{compose.t('messages.sendViaEmailHint', 'Recipients will receive an email copy with a secure link.')}</p>\n </div>\n <Switch checked={compose.sendViaEmail} onCheckedChange={compose.setSendViaEmail} />\n </div>\n )\n}\n\nfunction ReplyForwardOptionsRow({ compose }: ComposeProps) {\n if (compose.variant !== 'reply' && compose.variant !== 'forward') {\n return null\n }\n\n return (\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {compose.variant === 'forward' ? (\n <div className=\"flex items-center justify-between rounded border px-3 py-2\">\n <div>\n <p className=\"text-sm font-medium\">{compose.t('messages.includeAttachments', 'Include attachments')}</p>\n <p className=\"text-xs text-muted-foreground\">{compose.t('messages.includeAttachmentsHint', 'Carry over attachments from the original message.')}</p>\n </div>\n <Switch checked={compose.includeAttachments} onCheckedChange={compose.setIncludeAttachments} />\n </div>\n ) : (\n <div className=\"flex items-center justify-between rounded border px-3 py-2\">\n <div>\n <p className=\"text-sm font-medium\">{compose.t('messages.replyAll', 'Reply all')}</p>\n <p className=\"text-xs text-muted-foreground\">{compose.t('messages.replyAllHint', 'Include all original recipients.')}</p>\n </div>\n <Switch checked={compose.replyAll} onCheckedChange={compose.setReplyAll} />\n </div>\n )}\n <div className=\"flex items-center justify-between rounded border px-3 py-2\">\n <div>\n <p className=\"text-sm font-medium\">{compose.t('messages.sendViaEmail', 'Also send via email')}</p>\n <p className=\"text-xs text-muted-foreground\">{compose.t('messages.sendViaEmailHint', 'Recipients will receive an email copy with a secure link.')}</p>\n </div>\n <Switch checked={compose.sendViaEmail} onCheckedChange={compose.setSendViaEmail} />\n </div>\n </div>\n )\n}\n\nfunction MessageComposeFormBody({ compose }: ComposeProps) {\n return (\n <div className=\"space-y-4\" onKeyDown={compose.handleKeyDown}>\n {compose.contextPreview ? (\n <div className=\"rounded border bg-muted/30 p-3 text-sm\">\n {compose.contextPreview}\n </div>\n ) : null}\n {compose.variant === 'compose' ? <ComposeModeFields compose={compose} /> : null}\n {compose.variant === 'reply' ? <ReplyModeFields compose={compose} /> : null}\n {compose.variant === 'forward' ? <ForwardModeFields compose={compose} /> : null}\n <ReplyForwardOptionsRow compose={compose} />\n <EmailDeliverySection compose={compose} />\n {compose.submitError ? <p className=\"text-sm text-destructive\">{compose.submitError}</p> : null}\n </div>\n )\n}\n\nexport function createMessageComposeFormGroups(compose: UseMessageComposeResult): CrudField[] {\n return [{\n id: 'composer',\n label: '',\n type: 'custom',\n component: () => <MessageComposeFormBody compose={compose} />,\n }]\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import * as React from 'react'\nimport { FileCode, Globe, Lock } from 'lucide-react'\nimport { type CrudField } from '../CrudForm'\nimport { IconButton } from '../../primitives/icon-button'\nimport { Input } from '../../primitives/input'\nimport { EmailInput } from '../../primitives/email-input'\nimport { Label } from '../../primitives/label'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '../../primitives/select'\nimport { Switch } from '../../primitives/switch'\nimport { SwitchField } from '../../primitives/switch-field'\nimport { AttachmentsSection } from '../detail/AttachmentsSection'\nimport { SwitchableMarkdownInput } from '../inputs/SwitchableMarkdownInput'\nimport { TagsInput } from '../inputs/TagsInput'\nimport { MessagePrioritySelector } from './MessagePrioritySelector'\nimport type { UseMessageComposeResult } from './useMessageCompose'\n\ntype ComposeProps = {\n compose: UseMessageComposeResult\n}\n\nfunction RecipientTagsInput({ compose }: ComposeProps) {\n return (\n <TagsInput\n value={compose.recipientIds}\n onChange={compose.setRecipientIds}\n selectedOptions={compose.selectedRecipientOptions}\n resolveLabel={compose.resolveRecipientLabel}\n loadSuggestions={compose.loadRecipientSuggestions}\n placeholder={compose.t('messages.placeholders.recipients', 'Search recipients...')}\n allowCustomValues={false}\n showSuggestionsOnFocus={false}\n />\n )\n}\n\nfunction VisibilitySelector({ compose }: ComposeProps) {\n return (\n <>\n <Label>{compose.t('messages.visibility', 'Visibility')}</Label>\n <div\n className=\"inline-flex items-center gap-1 rounded-md border bg-background p-1\"\n role=\"radiogroup\"\n aria-label={compose.t('messages.visibility', 'Visibility')}\n >\n <IconButton\n type=\"button\"\n size=\"xs\"\n variant={compose.visibility === 'internal' ? 'outline' : 'ghost'}\n role=\"radio\"\n aria-checked={compose.visibility === 'internal'}\n aria-label={compose.t('messages.visibilityInternal', 'Internal')}\n title={compose.t('messages.visibilityInternal', 'Internal')}\n className=\"h-7 w-7\"\n onClick={() => compose.setVisibility('internal')}\n >\n <Lock className=\"h-3.5 w-3.5\" />\n </IconButton>\n <IconButton\n type=\"button\"\n size=\"xs\"\n variant={compose.visibility === 'public' ? 'outline' : 'ghost'}\n role=\"radio\"\n aria-checked={compose.visibility === 'public'}\n aria-label={compose.t('messages.visibilityPublic', 'Public')}\n title={compose.t('messages.visibilityPublic', 'Public')}\n className=\"h-7 w-7\"\n onClick={() => compose.setVisibility('public')}\n >\n <Globe className=\"h-3.5 w-3.5\" />\n </IconButton>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {compose.visibility === 'public'\n ? compose.t('messages.visibilityPublicHint', 'Public messages are sent to external email only.')\n : compose.t('messages.visibilityInternalHint', 'Internal messages are sent to selected system users.')}\n </p>\n </>\n )\n}\n\nfunction ContextActionsSection({ compose }: ComposeProps) {\n if (!compose.shouldShowContextActions) return null\n\n return (\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {compose.normalizedRequiredActionMode === 'optional' ? (\n <div className=\"flex items-center justify-between rounded border px-3 py-2 sm:col-span-2\">\n <div>\n <p className=\"text-sm font-medium\">\n {compose.t('messages.composer.objectPicker.actionRequiredLabel', 'Action required')}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {compose.t('messages.composer.objectPicker.actionRequiredHint', 'Mark this object as requiring recipient action.')}\n </p>\n </div>\n <Switch checked={compose.contextActionRequired} onCheckedChange={compose.setContextActionRequired} />\n </div>\n ) : null}\n {compose.normalizedRequiredActionMode === 'required' || compose.contextActionRequired ? (\n <div className=\"space-y-2 sm:col-span-2\">\n <Label htmlFor=\"messages-compose-context-action-type\">\n {compose.t('messages.composer.objectPicker.actionTypeLabel', 'Action type')}\n </Label>\n <Select\n value={compose.contextActionType || undefined}\n onValueChange={(value) => compose.setContextActionType(value || '')}\n >\n <SelectTrigger id=\"messages-compose-context-action-type\">\n <SelectValue placeholder={compose.t('messages.composer.objectPicker.actionTypePlaceholder', 'Select action')} />\n </SelectTrigger>\n <SelectContent>\n {compose.contextActionOptions.map((option) => (\n <SelectItem key={option.id} value={option.id}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction PrioritySelector({ compose }: ComposeProps) {\n return (\n <>\n <Label>{compose.t('messages.priority', 'Priority')}</Label>\n <MessagePrioritySelector\n value={compose.priority}\n onChange={compose.setPriority}\n t={compose.t}\n />\n </>\n )\n}\n\nfunction MarkdownBodySection({\n compose,\n label,\n placeholder,\n inputId,\n rows,\n textareaClassName,\n}: ComposeProps & {\n label: string\n placeholder: string\n inputId: string\n rows: number\n textareaClassName: string\n}) {\n return (\n <div className=\"space-y-2\">\n <div className=\"flex items-center justify-between gap-2\">\n <Label htmlFor={inputId}>{label}</Label>\n <IconButton\n type=\"button\"\n size=\"sm\"\n variant={compose.bodyFormat === 'markdown' ? 'outline' : 'ghost'}\n aria-pressed={compose.bodyFormat === 'markdown'}\n onClick={() => compose.setBodyFormat((previousValue) => (previousValue === 'markdown' ? 'text' : 'markdown'))}\n title={compose.t('messages.bodyFormat.toggle', 'Toggle markdown')}\n >\n <FileCode className=\"h-4 w-4\" />\n </IconButton>\n </div>\n <div id={inputId}>\n <SwitchableMarkdownInput\n value={compose.body}\n onChange={compose.setBody}\n isMarkdownEnabled={compose.bodyFormat === 'markdown'}\n rows={rows}\n placeholder={placeholder}\n textareaClassName={textareaClassName}\n />\n </div>\n </div>\n )\n}\n\nfunction ComposeModeFields({ compose }: ComposeProps) {\n return (\n <>\n <div className=\"space-y-3\">\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"space-y-2\">\n {compose.visibility === 'public' ? (\n <>\n <Label htmlFor=\"messages-compose-external-email\">{compose.t('messages.externalEmail', 'External email')}</Label>\n <EmailInput\n id=\"messages-compose-external-email\"\n value={compose.externalEmail}\n onChange={(event) => compose.setExternalEmail(event.target.value)}\n placeholder={compose.t('messages.placeholders.externalEmail', 'name@example.com')}\n />\n </>\n ) : (\n <>\n <Label htmlFor=\"messages-compose-recipients\">{compose.t('messages.to', 'To')}</Label>\n <RecipientTagsInput compose={compose} />\n </>\n )}\n </div>\n\n <div className=\"space-y-2\">\n <VisibilitySelector compose={compose} />\n </div>\n </div>\n\n <ContextActionsSection compose={compose} />\n </div>\n\n <div className=\"grid gap-3 sm:grid-cols-2\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"messages-compose-subject\">{compose.t('messages.subject', 'Subject')}</Label>\n <Input\n id=\"messages-compose-subject\"\n value={compose.subject}\n onChange={(event) => compose.setSubject(event.target.value)}\n placeholder={compose.t('messages.placeholders.subject', 'Enter subject...')}\n />\n </div>\n\n <div className=\"space-y-2\">\n <PrioritySelector compose={compose} />\n </div>\n </div>\n\n <MarkdownBodySection\n compose={compose}\n label={compose.t('messages.body', 'Message')}\n placeholder={compose.t('messages.placeholders.body', 'Write your message...')}\n inputId=\"messages-compose-body\"\n rows={8}\n textareaClassName=\"min-h-[180px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n\n <div className=\"space-y-2\">\n <Label>{compose.t('messages.attachedFiles', 'Attachments')}</Label>\n <AttachmentsSection\n entityId={compose.attachmentEntityId}\n recordId={compose.attachmentRecordId}\n showHeader={false}\n compact\n onChanged={() => {\n void compose.loadAttachmentIds().catch(() => null)\n }}\n />\n </div>\n </>\n )\n}\n\nfunction ReplyModeFields({ compose }: ComposeProps) {\n return (\n <>\n <MarkdownBodySection\n compose={compose}\n label={compose.t('messages.replyBody', 'Reply')}\n placeholder={compose.t('messages.placeholders.replyBody', 'Write your reply...')}\n inputId=\"messages-compose-body\"\n rows={8}\n textareaClassName=\"min-h-[180px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n\n <div className=\"space-y-2\">\n <Label>{compose.t('messages.attachedFiles', 'Attachments')}</Label>\n <AttachmentsSection\n entityId={compose.attachmentEntityId}\n recordId={compose.attachmentRecordId}\n showHeader={false}\n compact\n onChanged={() => {\n void compose.loadAttachmentIds().catch(() => null)\n }}\n />\n </div>\n </>\n )\n}\n\nfunction ForwardModeFields({ compose }: ComposeProps) {\n return (\n <>\n <div className=\"space-y-2\">\n <Label htmlFor=\"messages-compose-recipients\">{compose.t('messages.to', 'To')}</Label>\n <RecipientTagsInput compose={compose} />\n </div>\n\n <MarkdownBodySection\n compose={compose}\n label={compose.t('messages.forwardContent', 'Forwarded content')}\n placeholder={compose.t('messages.placeholders.forwardContent', 'Review and edit forwarded content...')}\n inputId=\"messages-forward-note\"\n rows={6}\n textareaClassName=\"min-h-[140px] w-full rounded-md border bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n />\n </>\n )\n}\n\nfunction EmailDeliverySection({ compose }: ComposeProps) {\n if (compose.variant === 'reply' || compose.variant === 'forward') {\n return null\n }\n\n if (compose.isComposePublicVisibility) {\n return (\n <div className=\"rounded border px-3 py-2\">\n <p className=\"text-sm font-medium\">{compose.t('messages.sendViaEmail', 'Also send via email')}</p>\n <p className=\"text-xs text-muted-foreground\">{compose.t('messages.sendViaEmailForcedPublic', 'For public visibility, email delivery is always enabled.')}</p>\n </div>\n )\n }\n\n return (\n <SwitchField\n containerClassName=\"rounded border px-3 py-2\"\n label={compose.t('messages.sendViaEmail', 'Also send via email')}\n description={compose.t('messages.sendViaEmailHint', 'Recipients will receive an email copy with a secure link.')}\n checked={compose.sendViaEmail}\n onCheckedChange={compose.setSendViaEmail}\n />\n )\n}\n\nfunction ReplyForwardOptionsRow({ compose }: ComposeProps) {\n if (compose.variant !== 'reply' && compose.variant !== 'forward') {\n return null\n }\n\n return (\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {compose.variant === 'forward' ? (\n <SwitchField\n containerClassName=\"rounded border px-3 py-2\"\n label={compose.t('messages.includeAttachments', 'Include attachments')}\n description={compose.t('messages.includeAttachmentsHint', 'Carry over attachments from the original message.')}\n checked={compose.includeAttachments}\n onCheckedChange={compose.setIncludeAttachments}\n />\n ) : (\n <SwitchField\n containerClassName=\"rounded border px-3 py-2\"\n label={compose.t('messages.replyAll', 'Reply all')}\n description={compose.t('messages.replyAllHint', 'Include all original recipients.')}\n checked={compose.replyAll}\n onCheckedChange={compose.setReplyAll}\n />\n )}\n <SwitchField\n containerClassName=\"rounded border px-3 py-2\"\n label={compose.t('messages.sendViaEmail', 'Also send via email')}\n description={compose.t('messages.sendViaEmailHint', 'Recipients will receive an email copy with a secure link.')}\n checked={compose.sendViaEmail}\n onCheckedChange={compose.setSendViaEmail}\n />\n </div>\n )\n}\n\nfunction MessageComposeFormBody({ compose }: ComposeProps) {\n return (\n <div className=\"space-y-4\" onKeyDown={compose.handleKeyDown}>\n {compose.contextPreview ? (\n <div className=\"rounded border bg-muted/30 p-3 text-sm\">\n {compose.contextPreview}\n </div>\n ) : null}\n {compose.variant === 'compose' ? <ComposeModeFields compose={compose} /> : null}\n {compose.variant === 'reply' ? <ReplyModeFields compose={compose} /> : null}\n {compose.variant === 'forward' ? <ForwardModeFields compose={compose} /> : null}\n <ReplyForwardOptionsRow compose={compose} />\n <EmailDeliverySection compose={compose} />\n {compose.submitError ? <p className=\"text-sm text-destructive\">{compose.submitError}</p> : null}\n </div>\n )\n}\n\nexport function createMessageComposeFormGroups(compose: UseMessageComposeResult): CrudField[] {\n return [{\n id: 'composer',\n label: '',\n type: 'custom',\n component: () => <MessageComposeFormBody compose={compose} />,\n }]\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -15,10 +15,12 @@ const SwitchField = React.forwardRef(({
15
15
  contentClassName,
16
16
  className,
17
17
  disabled,
18
+ "aria-describedby": ariaDescribedBy,
18
19
  ...switchProps
19
20
  }, ref) => {
20
21
  const fallbackId = React.useId();
21
22
  const id = idProp ?? fallbackId;
23
+ const descriptionId = `${id}-description`;
22
24
  const hasMultiLine = Boolean(description || sublabel || link);
23
25
  const switchEl = /* @__PURE__ */ jsx(
24
26
  Switch,
@@ -26,6 +28,7 @@ const SwitchField = React.forwardRef(({
26
28
  ref,
27
29
  id,
28
30
  disabled,
31
+ "aria-describedby": ariaDescribedBy ?? (description ? descriptionId : void 0),
29
32
  className: cn(hasMultiLine && "mt-0.5", className),
30
33
  ...switchProps
31
34
  }
@@ -47,7 +50,7 @@ const SwitchField = React.forwardRef(({
47
50
  sublabel ? /* @__PURE__ */ jsx("span", { className: "text-xs leading-4 text-muted-foreground select-none", children: sublabel }) : null,
48
51
  badge ? /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0", children: badge }) : null
49
52
  ] }),
50
- description ? /* @__PURE__ */ jsx("p", { className: "text-xs leading-4 text-muted-foreground", children: description }) : null
53
+ description ? /* @__PURE__ */ jsx("p", { id: descriptionId, className: "text-xs leading-4 text-muted-foreground", children: description }) : null
51
54
  ] }),
52
55
  link ? /* @__PURE__ */ jsx("div", { className: "flex", children: link }) : null
53
56
  ] });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/primitives/switch-field.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Switch } from './switch'\n\nexport type SwitchFieldProps = Omit<React.ComponentProps<typeof Switch>, 'id'> & {\n id?: string\n label: React.ReactNode\n sublabel?: React.ReactNode\n description?: React.ReactNode\n badge?: React.ReactNode\n link?: React.ReactNode\n /** When true, renders the switch on the left of the label content. */\n flip?: boolean\n containerClassName?: string\n contentClassName?: string\n}\n\nexport const SwitchField = React.forwardRef<\n React.ElementRef<typeof Switch>,\n SwitchFieldProps\n>(({\n id: idProp,\n label,\n sublabel,\n description,\n badge,\n link,\n flip = false,\n containerClassName,\n contentClassName,\n className,\n disabled,\n ...switchProps\n}, ref) => {\n // useId is SSR/HMR-stable; counter-based fallbacks drift on hydration.\n const fallbackId = React.useId()\n const id = idProp ?? fallbackId\n\n const hasMultiLine = Boolean(description || sublabel || link)\n const switchEl = (\n <Switch\n ref={ref}\n id={id}\n disabled={disabled}\n className={cn(hasMultiLine && 'mt-0.5', className)}\n {...switchProps}\n />\n )\n\n const content = (\n <div className={cn('flex flex-1 min-w-0 flex-col gap-2.5', contentClassName)}>\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex flex-wrap items-center gap-1\">\n <label\n htmlFor={id}\n className={cn(\n 'text-sm font-medium leading-5 text-foreground select-none',\n disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer'\n )}\n >\n {label}\n </label>\n {sublabel ? (\n <span className=\"text-xs leading-4 text-muted-foreground select-none\">{sublabel}</span>\n ) : null}\n {badge ? <span className=\"inline-flex shrink-0\">{badge}</span> : null}\n </div>\n {description ? (\n <p className=\"text-xs leading-4 text-muted-foreground\">{description}</p>\n ) : null}\n </div>\n {link ? <div className=\"flex\">{link}</div> : null}\n </div>\n )\n\n return (\n <div\n className={cn(\n 'flex gap-3',\n hasMultiLine ? 'items-start' : 'items-center',\n containerClassName\n )}\n >\n {flip ? (\n <>\n {switchEl}\n {content}\n </>\n ) : (\n <>\n {content}\n {switchEl}\n </>\n )}\n </div>\n )\n})\nSwitchField.displayName = 'SwitchField'\n"],
5
- "mappings": ";AA0CI,SA4CI,UA5CJ,KAYI,YAZJ;AAxCJ,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,cAAc;AAehB,MAAM,cAAc,MAAM,WAG/B,CAAC;AAAA,EACD,IAAI;AAAA,EACJ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAG,QAAQ;AAET,QAAM,aAAa,MAAM,MAAM;AAC/B,QAAM,KAAK,UAAU;AAErB,QAAM,eAAe,QAAQ,eAAe,YAAY,IAAI;AAC5D,QAAM,WACJ;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,GAAG,gBAAgB,UAAU,SAAS;AAAA,MAChD,GAAG;AAAA;AAAA,EACN;AAGF,QAAM,UACJ,qBAAC,SAAI,WAAW,GAAG,wCAAwC,gBAAgB,GACzE;AAAA,yBAAC,SAAI,WAAU,uBACb;AAAA,2BAAC,SAAI,WAAU,qCACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS;AAAA,YACT,WAAW;AAAA,cACT;AAAA,cACA,WAAW,kCAAkC;AAAA,YAC/C;AAAA,YAEC;AAAA;AAAA,QACH;AAAA,QACC,WACC,oBAAC,UAAK,WAAU,uDAAuD,oBAAS,IAC9E;AAAA,QACH,QAAQ,oBAAC,UAAK,WAAU,wBAAwB,iBAAM,IAAU;AAAA,SACnE;AAAA,MACC,cACC,oBAAC,OAAE,WAAU,2CAA2C,uBAAY,IAClE;AAAA,OACN;AAAA,IACC,OAAO,oBAAC,SAAI,WAAU,QAAQ,gBAAK,IAAS;AAAA,KAC/C;AAGF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,eAAe,gBAAgB;AAAA,QAC/B;AAAA,MACF;AAAA,MAEC,iBACC,iCACG;AAAA;AAAA,QACA;AAAA,SACH,IAEA,iCACG;AAAA;AAAA,QACA;AAAA,SACH;AAAA;AAAA,EAEJ;AAEJ,CAAC;AACD,YAAY,cAAc;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Switch } from './switch'\n\nexport type SwitchFieldProps = Omit<React.ComponentProps<typeof Switch>, 'id'> & {\n id?: string\n label: React.ReactNode\n sublabel?: React.ReactNode\n description?: React.ReactNode\n badge?: React.ReactNode\n link?: React.ReactNode\n /** When true, renders the switch on the left of the label content. */\n flip?: boolean\n containerClassName?: string\n contentClassName?: string\n}\n\nexport const SwitchField = React.forwardRef<\n React.ElementRef<typeof Switch>,\n SwitchFieldProps\n>(({\n id: idProp,\n label,\n sublabel,\n description,\n badge,\n link,\n flip = false,\n containerClassName,\n contentClassName,\n className,\n disabled,\n 'aria-describedby': ariaDescribedBy,\n ...switchProps\n}, ref) => {\n // useId is SSR/HMR-stable; counter-based fallbacks drift on hydration.\n const fallbackId = React.useId()\n const id = idProp ?? fallbackId\n const descriptionId = `${id}-description`\n\n const hasMultiLine = Boolean(description || sublabel || link)\n const switchEl = (\n <Switch\n ref={ref}\n id={id}\n disabled={disabled}\n aria-describedby={ariaDescribedBy ?? (description ? descriptionId : undefined)}\n className={cn(hasMultiLine && 'mt-0.5', className)}\n {...switchProps}\n />\n )\n\n const content = (\n <div className={cn('flex flex-1 min-w-0 flex-col gap-2.5', contentClassName)}>\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex flex-wrap items-center gap-1\">\n <label\n htmlFor={id}\n className={cn(\n 'text-sm font-medium leading-5 text-foreground select-none',\n disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer'\n )}\n >\n {label}\n </label>\n {sublabel ? (\n <span className=\"text-xs leading-4 text-muted-foreground select-none\">{sublabel}</span>\n ) : null}\n {badge ? <span className=\"inline-flex shrink-0\">{badge}</span> : null}\n </div>\n {description ? (\n <p id={descriptionId} className=\"text-xs leading-4 text-muted-foreground\">{description}</p>\n ) : null}\n </div>\n {link ? <div className=\"flex\">{link}</div> : null}\n </div>\n )\n\n return (\n <div\n className={cn(\n 'flex gap-3',\n hasMultiLine ? 'items-start' : 'items-center',\n containerClassName\n )}\n >\n {flip ? (\n <>\n {switchEl}\n {content}\n </>\n ) : (\n <>\n {content}\n {switchEl}\n </>\n )}\n </div>\n )\n})\nSwitchField.displayName = 'SwitchField'\n"],
5
+ "mappings": ";AA4CI,SA6CI,UA7CJ,KAaI,YAbJ;AA1CJ,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,cAAc;AAehB,MAAM,cAAc,MAAM,WAG/B,CAAC;AAAA,EACD,IAAI;AAAA,EACJ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,oBAAoB;AAAA,EACpB,GAAG;AACL,GAAG,QAAQ;AAET,QAAM,aAAa,MAAM,MAAM;AAC/B,QAAM,KAAK,UAAU;AACrB,QAAM,gBAAgB,GAAG,EAAE;AAE3B,QAAM,eAAe,QAAQ,eAAe,YAAY,IAAI;AAC5D,QAAM,WACJ;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,oBAAkB,oBAAoB,cAAc,gBAAgB;AAAA,MACpE,WAAW,GAAG,gBAAgB,UAAU,SAAS;AAAA,MAChD,GAAG;AAAA;AAAA,EACN;AAGF,QAAM,UACJ,qBAAC,SAAI,WAAW,GAAG,wCAAwC,gBAAgB,GACzE;AAAA,yBAAC,SAAI,WAAU,uBACb;AAAA,2BAAC,SAAI,WAAU,qCACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS;AAAA,YACT,WAAW;AAAA,cACT;AAAA,cACA,WAAW,kCAAkC;AAAA,YAC/C;AAAA,YAEC;AAAA;AAAA,QACH;AAAA,QACC,WACC,oBAAC,UAAK,WAAU,uDAAuD,oBAAS,IAC9E;AAAA,QACH,QAAQ,oBAAC,UAAK,WAAU,wBAAwB,iBAAM,IAAU;AAAA,SACnE;AAAA,MACC,cACC,oBAAC,OAAE,IAAI,eAAe,WAAU,2CAA2C,uBAAY,IACrF;AAAA,OACN;AAAA,IACC,OAAO,oBAAC,SAAI,WAAU,QAAQ,gBAAK,IAAS;AAAA,KAC/C;AAGF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,eAAe,gBAAgB;AAAA,QAC/B;AAAA,MACF;AAAA,MAEC,iBACC,iCACG;AAAA;AAAA,QACA;AAAA,SACH,IAEA,iCACG;AAAA;AAAA,QACA;AAAA,SACH;AAAA;AAAA,EAEJ;AAEJ,CAAC;AACD,YAAY,cAAc;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ui",
3
- "version": "0.7.1-develop.7194.1.ab4fc81f82",
3
+ "version": "0.7.1-develop.7200.1.8ba2d93d3f",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -155,14 +155,14 @@
155
155
  "remark-gfm": "^4.0.1"
156
156
  },
157
157
  "peerDependencies": {
158
- "@open-mercato/shared": "0.7.1-develop.7194.1.ab4fc81f82",
158
+ "@open-mercato/shared": "0.7.1-develop.7200.1.8ba2d93d3f",
159
159
  "react": ">=18.0.0",
160
160
  "react-dom": ">=18.0.0",
161
161
  "react-is": ">=18.0.0"
162
162
  },
163
163
  "devDependencies": {
164
164
  "@figma/code-connect": "^1.3.4",
165
- "@open-mercato/shared": "0.7.1-develop.7194.1.ab4fc81f82",
165
+ "@open-mercato/shared": "0.7.1-develop.7200.1.8ba2d93d3f",
166
166
  "@testing-library/dom": "^10.4.1",
167
167
  "@testing-library/jest-dom": "^7.0.1",
168
168
  "@testing-library/react": "^16.3.3",
@@ -13,6 +13,7 @@ import {
13
13
  SelectValue,
14
14
  } from '../../primitives/select'
15
15
  import { Switch } from '../../primitives/switch'
16
+ import { SwitchField } from '../../primitives/switch-field'
16
17
  import { AttachmentsSection } from '../detail/AttachmentsSection'
17
18
  import { SwitchableMarkdownInput } from '../inputs/SwitchableMarkdownInput'
18
19
  import { TagsInput } from '../inputs/TagsInput'
@@ -319,13 +320,13 @@ function EmailDeliverySection({ compose }: ComposeProps) {
319
320
  }
320
321
 
321
322
  return (
322
- <div className="flex items-center justify-between rounded border px-3 py-2">
323
- <div>
324
- <p className="text-sm font-medium">{compose.t('messages.sendViaEmail', 'Also send via email')}</p>
325
- <p className="text-xs text-muted-foreground">{compose.t('messages.sendViaEmailHint', 'Recipients will receive an email copy with a secure link.')}</p>
326
- </div>
327
- <Switch checked={compose.sendViaEmail} onCheckedChange={compose.setSendViaEmail} />
328
- </div>
323
+ <SwitchField
324
+ containerClassName="rounded border px-3 py-2"
325
+ label={compose.t('messages.sendViaEmail', 'Also send via email')}
326
+ description={compose.t('messages.sendViaEmailHint', 'Recipients will receive an email copy with a secure link.')}
327
+ checked={compose.sendViaEmail}
328
+ onCheckedChange={compose.setSendViaEmail}
329
+ />
329
330
  )
330
331
  }
331
332
 
@@ -337,29 +338,29 @@ function ReplyForwardOptionsRow({ compose }: ComposeProps) {
337
338
  return (
338
339
  <div className="grid gap-3 sm:grid-cols-2">
339
340
  {compose.variant === 'forward' ? (
340
- <div className="flex items-center justify-between rounded border px-3 py-2">
341
- <div>
342
- <p className="text-sm font-medium">{compose.t('messages.includeAttachments', 'Include attachments')}</p>
343
- <p className="text-xs text-muted-foreground">{compose.t('messages.includeAttachmentsHint', 'Carry over attachments from the original message.')}</p>
344
- </div>
345
- <Switch checked={compose.includeAttachments} onCheckedChange={compose.setIncludeAttachments} />
346
- </div>
341
+ <SwitchField
342
+ containerClassName="rounded border px-3 py-2"
343
+ label={compose.t('messages.includeAttachments', 'Include attachments')}
344
+ description={compose.t('messages.includeAttachmentsHint', 'Carry over attachments from the original message.')}
345
+ checked={compose.includeAttachments}
346
+ onCheckedChange={compose.setIncludeAttachments}
347
+ />
347
348
  ) : (
348
- <div className="flex items-center justify-between rounded border px-3 py-2">
349
- <div>
350
- <p className="text-sm font-medium">{compose.t('messages.replyAll', 'Reply all')}</p>
351
- <p className="text-xs text-muted-foreground">{compose.t('messages.replyAllHint', 'Include all original recipients.')}</p>
352
- </div>
353
- <Switch checked={compose.replyAll} onCheckedChange={compose.setReplyAll} />
354
- </div>
349
+ <SwitchField
350
+ containerClassName="rounded border px-3 py-2"
351
+ label={compose.t('messages.replyAll', 'Reply all')}
352
+ description={compose.t('messages.replyAllHint', 'Include all original recipients.')}
353
+ checked={compose.replyAll}
354
+ onCheckedChange={compose.setReplyAll}
355
+ />
355
356
  )}
356
- <div className="flex items-center justify-between rounded border px-3 py-2">
357
- <div>
358
- <p className="text-sm font-medium">{compose.t('messages.sendViaEmail', 'Also send via email')}</p>
359
- <p className="text-xs text-muted-foreground">{compose.t('messages.sendViaEmailHint', 'Recipients will receive an email copy with a secure link.')}</p>
360
- </div>
361
- <Switch checked={compose.sendViaEmail} onCheckedChange={compose.setSendViaEmail} />
362
- </div>
357
+ <SwitchField
358
+ containerClassName="rounded border px-3 py-2"
359
+ label={compose.t('messages.sendViaEmail', 'Also send via email')}
360
+ description={compose.t('messages.sendViaEmailHint', 'Recipients will receive an email copy with a secure link.')}
361
+ checked={compose.sendViaEmail}
362
+ onCheckedChange={compose.setSendViaEmail}
363
+ />
363
364
  </div>
364
365
  )
365
366
  }
@@ -0,0 +1,38 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import * as React from 'react'
4
+ import { render, screen } from '@testing-library/react'
5
+ import { SwitchField } from '../switch-field'
6
+
7
+ describe('SwitchField', () => {
8
+ it('exposes an accessible name derived from the label', () => {
9
+ render(<SwitchField label="Also send via email" checked={false} onCheckedChange={() => {}} />)
10
+
11
+ expect(screen.getByRole('switch', { name: 'Also send via email' })).toBeInTheDocument()
12
+ })
13
+
14
+ it('associates the description text via aria-describedby', () => {
15
+ render(
16
+ <SwitchField
17
+ label="Also send via email"
18
+ description="Recipients will receive an email copy with a secure link."
19
+ checked={false}
20
+ onCheckedChange={() => {}}
21
+ />,
22
+ )
23
+
24
+ const control = screen.getByRole('switch', { name: 'Also send via email' })
25
+ const describedById = control.getAttribute('aria-describedby')
26
+ expect(describedById).toBeTruthy()
27
+ expect(screen.getByText('Recipients will receive an email copy with a secure link.')).toHaveAttribute(
28
+ 'id',
29
+ describedById,
30
+ )
31
+ })
32
+
33
+ it('does not set aria-describedby when there is no description', () => {
34
+ render(<SwitchField label="Also send via email" checked={false} onCheckedChange={() => {}} />)
35
+
36
+ expect(screen.getByRole('switch', { name: 'Also send via email' })).not.toHaveAttribute('aria-describedby')
37
+ })
38
+ })
@@ -32,11 +32,13 @@ export const SwitchField = React.forwardRef<
32
32
  contentClassName,
33
33
  className,
34
34
  disabled,
35
+ 'aria-describedby': ariaDescribedBy,
35
36
  ...switchProps
36
37
  }, ref) => {
37
38
  // useId is SSR/HMR-stable; counter-based fallbacks drift on hydration.
38
39
  const fallbackId = React.useId()
39
40
  const id = idProp ?? fallbackId
41
+ const descriptionId = `${id}-description`
40
42
 
41
43
  const hasMultiLine = Boolean(description || sublabel || link)
42
44
  const switchEl = (
@@ -44,6 +46,7 @@ export const SwitchField = React.forwardRef<
44
46
  ref={ref}
45
47
  id={id}
46
48
  disabled={disabled}
49
+ aria-describedby={ariaDescribedBy ?? (description ? descriptionId : undefined)}
47
50
  className={cn(hasMultiLine && 'mt-0.5', className)}
48
51
  {...switchProps}
49
52
  />
@@ -68,7 +71,7 @@ export const SwitchField = React.forwardRef<
68
71
  {badge ? <span className="inline-flex shrink-0">{badge}</span> : null}
69
72
  </div>
70
73
  {description ? (
71
- <p className="text-xs leading-4 text-muted-foreground">{description}</p>
74
+ <p id={descriptionId} className="text-xs leading-4 text-muted-foreground">{description}</p>
72
75
  ) : null}
73
76
  </div>
74
77
  {link ? <div className="flex">{link}</div> : null}