@ctrl-ui/react 0.8.4 → 0.8.5

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.
Files changed (134) hide show
  1. package/dist/audio-visualizer.js +1 -1
  2. package/dist/chat-composer-editor/extensions/mention.js +4 -0
  3. package/dist/chat-layout.d.ts +5 -0
  4. package/dist/chat-layout.js +61 -20
  5. package/dist/chat-message.d.ts +5 -0
  6. package/dist/chat-message.js +19 -2
  7. package/dist/context.js +0 -2
  8. package/dist/email/email-brand.d.ts +60 -0
  9. package/dist/email/email-brand.js +101 -0
  10. package/dist/email/email-code.d.ts +18 -0
  11. package/dist/email/email-code.js +172 -0
  12. package/dist/email/email-footer.d.ts +63 -0
  13. package/dist/email/email-footer.js +98 -0
  14. package/dist/email/email.d.ts +60 -11
  15. package/dist/email/email.js +155 -24
  16. package/dist/email/templates.d.ts +54 -9
  17. package/dist/email/templates.js +314 -116
  18. package/dist/email/theme.d.ts +13 -5
  19. package/dist/email/theme.js +30 -8
  20. package/dist/hooks/use-chat-message.d.ts +1 -0
  21. package/dist/hooks/use-chat-message.js +1 -0
  22. package/dist/hooks/use-chat-thread-scroll.d.ts +8 -0
  23. package/dist/hooks/use-chat-thread-scroll.js +66 -0
  24. package/dist/hooks/use-user-ask.d.ts +6 -4
  25. package/dist/hooks/use-user-ask.js +56 -17
  26. package/dist/inline-attachment.js +1 -1
  27. package/dist/knob-contracts/action-bar-knobs.d.ts +1 -1
  28. package/dist/knob-contracts/action-bar-knobs.js +1 -1
  29. package/dist/knob-contracts/activity-knobs.d.ts +1 -1
  30. package/dist/knob-contracts/activity-knobs.js +2 -1
  31. package/dist/knob-contracts/audio-visualizer-knobs.d.ts +1 -1
  32. package/dist/knob-contracts/audio-visualizer-knobs.js +1 -0
  33. package/dist/knob-contracts/chat-composer-attachment-knobs.d.ts +1 -1
  34. package/dist/knob-contracts/chat-composer-attachment-knobs.js +2 -0
  35. package/dist/knob-contracts/chat-composer-knobs.d.ts +1 -1
  36. package/dist/knob-contracts/chat-composer-knobs.js +1 -0
  37. package/dist/knob-contracts/chat-layout-knobs.d.ts +1 -1
  38. package/dist/knob-contracts/chat-layout-knobs.js +2 -1
  39. package/dist/knob-contracts/chat-message-knobs.d.ts +1 -1
  40. package/dist/knob-contracts/chat-message-knobs.js +3 -1
  41. package/dist/knob-contracts/context-knobs.d.ts +1 -1
  42. package/dist/knob-contracts/context-knobs.js +3 -0
  43. package/dist/knob-contracts/inline-attachment-knobs.d.ts +1 -1
  44. package/dist/knob-contracts/inline-attachment-knobs.js +4 -0
  45. package/dist/knob-contracts/inline-citation-knobs.d.ts +1 -1
  46. package/dist/knob-contracts/inline-citation-knobs.js +3 -1
  47. package/dist/knob-contracts/page-layout-knobs.d.ts +4 -0
  48. package/dist/knob-contracts/page-layout-knobs.js +15 -0
  49. package/dist/knob-contracts/table-of-contents-knobs.d.ts +1 -1
  50. package/dist/knob-contracts/table-of-contents-knobs.js +3 -3
  51. package/dist/knob-contracts/task-list-knobs.d.ts +1 -1
  52. package/dist/knob-contracts/task-list-knobs.js +4 -0
  53. package/dist/knob-contracts/user-ask-knobs.d.ts +1 -1
  54. package/dist/knob-contracts/user-ask-knobs.js +4 -0
  55. package/dist/lib/phone-input-format.js +1 -1
  56. package/dist/markdown-block.js +1 -1
  57. package/dist/styles/index.css +2 -1
  58. package/dist/styles/recipes/action-bar.css +37 -5
  59. package/dist/styles/recipes/activity.css +36 -3
  60. package/dist/styles/recipes/alert.css +2 -0
  61. package/dist/styles/recipes/audio-visualizer.css +8 -1
  62. package/dist/styles/recipes/button-group.css +8 -0
  63. package/dist/styles/recipes/button.css +15 -0
  64. package/dist/styles/recipes/calendar.css +21 -0
  65. package/dist/styles/recipes/card.css +2 -0
  66. package/dist/styles/recipes/chat-composer-attachment.css +33 -2
  67. package/dist/styles/recipes/chat-composer.css +16 -2
  68. package/dist/styles/recipes/chat-layout.css +25 -1
  69. package/dist/styles/recipes/chat-message.css +69 -2
  70. package/dist/styles/recipes/choice.css +12 -0
  71. package/dist/styles/recipes/code-diff.css +1 -0
  72. package/dist/styles/recipes/code.css +25 -0
  73. package/dist/styles/recipes/context.css +30 -18
  74. package/dist/styles/recipes/dynamic-notification-input.css +6 -0
  75. package/dist/styles/recipes/dynamic-notification.css +1 -0
  76. package/dist/styles/recipes/environment-variables.css +1 -0
  77. package/dist/styles/recipes/field-parts.css +2 -0
  78. package/dist/styles/recipes/field.css +6 -0
  79. package/dist/styles/recipes/gradient-editor.css +17 -0
  80. package/dist/styles/recipes/inline-attachment.css +57 -14
  81. package/dist/styles/recipes/inline-citation.css +44 -6
  82. package/dist/styles/recipes/item.css +3 -1
  83. package/dist/styles/recipes/markdown-block.css +1 -0
  84. package/dist/styles/recipes/markdown.css +7 -0
  85. package/dist/styles/recipes/page-layout.css +203 -0
  86. package/dist/styles/recipes/pagination.css +16 -0
  87. package/dist/styles/recipes/popup-drawer.css +3 -1
  88. package/dist/styles/recipes/popup-toast.css +2 -0
  89. package/dist/styles/recipes/popup.css +8 -12
  90. package/dist/styles/recipes/range.css +24 -2
  91. package/dist/styles/recipes/rich-tooltip.css +1 -0
  92. package/dist/styles/recipes/source-badge.css +20 -0
  93. package/dist/styles/recipes/table-of-contents.css +10 -10
  94. package/dist/styles/recipes/tabs.css +16 -0
  95. package/dist/styles/recipes/task-list.css +61 -8
  96. package/dist/styles/recipes/thread-rail.css +19 -13
  97. package/dist/styles/recipes/toolbar.css +21 -0
  98. package/dist/styles/recipes/transcript-divider.css +1 -0
  99. package/dist/styles/recipes/user-ask.css +59 -16
  100. package/dist/styles/theme.css +34 -0
  101. package/dist/thread-rail.js +1 -1
  102. package/dist/ui/alert-dialog.js +2 -2
  103. package/dist/ui/autocomplete.js +1 -1
  104. package/dist/ui/color-picker.js +4 -4
  105. package/dist/ui/combobox.js +1 -1
  106. package/dist/ui/command.d.ts +5 -3
  107. package/dist/ui/command.js +27 -3
  108. package/dist/ui/context-menu.js +2 -2
  109. package/dist/ui/dialog.js +2 -2
  110. package/dist/ui/drawer.js +2 -2
  111. package/dist/ui/dropdown-menu.js +1 -1
  112. package/dist/ui/dropzone.js +4 -1
  113. package/dist/ui/gradient-editor.js +2 -2
  114. package/dist/ui/hover-card.js +1 -1
  115. package/dist/ui/infinite-canvas.js +1 -0
  116. package/dist/ui/menubar.js +2 -2
  117. package/dist/ui/navigation-menu.js +1 -1
  118. package/dist/ui/number-field.js +1 -1
  119. package/dist/ui/page-layout.d.ts +44 -0
  120. package/dist/ui/page-layout.js +108 -0
  121. package/dist/ui/phone-input.js +4 -4
  122. package/dist/ui/popover.js +1 -1
  123. package/dist/ui/resizable.js +1 -1
  124. package/dist/ui/rich-tooltip.js +1 -1
  125. package/dist/ui/select.js +1 -1
  126. package/dist/ui/sheet.js +2 -2
  127. package/dist/ui/sidebar-resize-rail.js +1 -1
  128. package/dist/ui/slider.js +1 -1
  129. package/dist/ui/toast.js +2 -2
  130. package/dist/ui/tooltip.js +1 -1
  131. package/dist/ui/trigger-menu.js +1 -1
  132. package/dist/user-ask.d.ts +5 -3
  133. package/dist/user-ask.js +38 -13
  134. package/package.json +3 -2
@@ -35,7 +35,7 @@ function AudioVisualizer({ levels, points, active = true, className, style, ...p
35
35
  const visibleLevel = MIN_VISIBLE_LEVEL + perceptualLevel * .88;
36
36
  const levelStyle = {
37
37
  "--_audio-visualizer-level-opacity": `${.48 + perceptualLevel * .52}`,
38
- clipPath: `inset(${(1 - visibleLevel) * 50}% 0 round var(--radius-control))`
38
+ clipPath: `inset(${(1 - visibleLevel) * 50}% 0 round var(--cui-audio-visualizer-bar-radius))`
39
39
  };
40
40
  return /* @__PURE__ */ jsx("span", {
41
41
  "data-control-ui": "audio-visualizer",
@@ -68,6 +68,10 @@ const mentionNode = {
68
68
  ["img", {
69
69
  src: icon,
70
70
  alt: "",
71
+ width: "16",
72
+ height: "16",
73
+ loading: "lazy",
74
+ decoding: "async",
71
75
  class: "shrink-0",
72
76
  "data-control-ui": "chat-composer",
73
77
  "data-control-family": "chat-composer",
@@ -1,3 +1,4 @@
1
+ import { Button } from "./ui/button.js";
1
2
  import { ChatLayoutKnobStyle } from "./knob-contracts/chat-layout-knobs.js";
2
3
  import { CSSProperties, ComponentProps, ReactNode } from "react";
3
4
  //#region src/chat-layout.d.ts
@@ -11,6 +12,10 @@ export type ChatThreadProps = ComponentProps<"div"> & {
11
12
  composer?: ReactNode;
12
13
  };
13
14
  export declare function ChatThread({ children, composer, className, ...props }: ChatThreadProps): import("react").JSX.Element;
15
+ export type ChatThreadScrollButtonProps = Omit<ComponentProps<typeof Button>, "children"> & {
16
+ label?: string;
17
+ };
18
+ export declare function ChatThreadScrollButton({ label, className, onClick, ...props }: ChatThreadScrollButtonProps): import("react").JSX.Element;
14
19
  export type ChatTurnProps = ComponentProps<"section"> & {
15
20
  from: "user" | "assistant";
16
21
  };
@@ -1,6 +1,11 @@
1
+ "use client";
1
2
  import { cn } from "./lib/cn.js";
2
3
  import { SkinAdornment } from "./skin-provider.js";
4
+ import { Button } from "./ui/button.js";
5
+ import { useChatThreadScroll } from "./hooks/use-chat-thread-scroll.js";
6
+ import { createContext, useContext } from "react";
3
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { ArrowDown } from "lucide-react";
4
9
  //#region src/chat-layout.tsx
5
10
  function ChatLayout({ children, chrome = "panel", className, ...props }) {
6
11
  return /* @__PURE__ */ jsxs("section", {
@@ -18,29 +23,65 @@ function ChatLayout({ children, chrome = "panel", className, ...props }) {
18
23
  }) : null, children]
19
24
  });
20
25
  }
26
+ const ChatThreadScrollContext = createContext(null);
21
27
  function ChatThread({ children, composer, className, ...props }) {
28
+ const { viewportRef, contentRef, atBottom, scrollToBottom } = useChatThreadScroll();
29
+ return /* @__PURE__ */ jsx(ChatThreadScrollContext.Provider, {
30
+ value: {
31
+ atBottom,
32
+ scrollToBottom
33
+ },
34
+ children: /* @__PURE__ */ jsx("div", {
35
+ ref: viewportRef,
36
+ "data-control-ui": "chat-thread",
37
+ "data-control-family": "chat-layout",
38
+ "data-chat-layout-kind": "thread",
39
+ "data-slot": "root",
40
+ "data-at-bottom": atBottom ? "" : void 0,
41
+ className: cn("min-h-0 min-w-0 flex-1 overflow-y-auto", className),
42
+ ...props,
43
+ children: /* @__PURE__ */ jsxs("div", {
44
+ ref: contentRef,
45
+ className: "relative flex min-h-full min-w-0 flex-col",
46
+ children: [/* @__PURE__ */ jsx("div", {
47
+ "data-control-ui": "chat-thread",
48
+ "data-control-family": "chat-layout",
49
+ "data-slot": "thread-content",
50
+ className: "flex min-w-0 flex-1 flex-col",
51
+ children
52
+ }), composer ? /* @__PURE__ */ jsx("div", {
53
+ "data-control-ui": "chat-thread",
54
+ "data-control-family": "chat-layout",
55
+ "data-slot": "dock",
56
+ className: "sticky bottom-0 z-10 shrink-0",
57
+ children: composer
58
+ }) : null]
59
+ })
60
+ })
61
+ });
62
+ }
63
+ function ChatThreadScrollButton({ label = "Scroll to latest", className, onClick, ...props }) {
64
+ const scroll = useContext(ChatThreadScrollContext);
65
+ if (!scroll) throw new Error("<ChatThreadScrollButton> must be rendered inside <ChatThread>.");
66
+ function handleClick(event) {
67
+ onClick?.(event);
68
+ if (!event.defaultPrevented) scroll?.scrollToBottom();
69
+ }
22
70
  return /* @__PURE__ */ jsx("div", {
23
71
  "data-control-ui": "chat-thread",
24
72
  "data-control-family": "chat-layout",
25
- "data-chat-layout-kind": "thread",
26
- "data-slot": "root",
27
- className: cn("min-h-0 min-w-0 flex-1 overflow-y-auto", className),
28
- ...props,
29
- children: /* @__PURE__ */ jsxs("div", {
30
- className: "relative flex min-h-full min-w-0 flex-col",
31
- children: [/* @__PURE__ */ jsx("div", {
32
- "data-control-ui": "chat-thread",
33
- "data-control-family": "chat-layout",
34
- "data-slot": "thread-content",
35
- className: "flex min-w-0 flex-1 flex-col",
36
- children
37
- }), composer ? /* @__PURE__ */ jsx("div", {
38
- "data-control-ui": "chat-thread",
39
- "data-control-family": "chat-layout",
40
- "data-slot": "dock",
41
- className: "sticky bottom-0 z-10 shrink-0",
42
- children: composer
43
- }) : null]
73
+ "data-slot": "scroll-anchor",
74
+ "data-hidden": scroll.atBottom ? "" : void 0,
75
+ children: /* @__PURE__ */ jsx(Button, {
76
+ ...props,
77
+ type: "button",
78
+ variant: "surface",
79
+ size: "sm",
80
+ iconOnly: true,
81
+ "aria-label": label,
82
+ onClick: handleClick,
83
+ className: cn("rounded-full", className),
84
+ children: /* @__PURE__ */ jsx(ArrowDown, { "aria-hidden": "true" })
44
85
  })
45
86
  });
46
87
  }
@@ -56,4 +97,4 @@ function ChatTurn({ from, children, className, ...props }) {
56
97
  });
57
98
  }
58
99
  //#endregion
59
- export { ChatLayout, ChatThread, ChatTurn };
100
+ export { ChatLayout, ChatThread, ChatThreadScrollButton, ChatTurn };
@@ -23,6 +23,11 @@ export type ChatMessageContentProps = Omit<ComponentProps<"div">, "style"> & {
23
23
  style?: CSSProperties & ChatMessageKnobStyle;
24
24
  };
25
25
  export declare function ChatMessageContent({ className, ...props }: ChatMessageContentProps): import("react").JSX.Element;
26
+ export type ChatMessagePendingProps = Omit<ComponentProps<"div">, "style" | "children"> & {
27
+ label?: string;
28
+ style?: CSSProperties & ChatMessageKnobStyle;
29
+ };
30
+ export declare function ChatMessagePending({ label, className, ...props }: ChatMessagePendingProps): import("react").JSX.Element | null;
26
31
  export type ChatMessageActionsProps = Omit<ComponentProps<"div">, "style"> & {
27
32
  style?: CSSProperties & ChatMessageKnobStyle;
28
33
  };
@@ -2,7 +2,7 @@
2
2
  import { cn } from "./lib/cn.js";
3
3
  import { useChatMessage } from "./hooks/use-chat-message.js";
4
4
  import { createContext, useContext } from "react";
5
- import { jsx } from "react/jsx-runtime";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
6
  //#region src/chat-message.tsx
7
7
  const ChatMessageContext = createContext(null);
8
8
  function useChatMessageContext() {
@@ -82,6 +82,23 @@ function ChatMessageContent({ className, ...props }) {
82
82
  ...props
83
83
  });
84
84
  }
85
+ function ChatMessagePending({ label = "Assistant is replying", className, ...props }) {
86
+ if (!useChatMessageContext().isPending) return null;
87
+ return /* @__PURE__ */ jsxs("div", {
88
+ role: "status",
89
+ "aria-label": label,
90
+ "data-control-ui": "chat-message",
91
+ "data-control-family": "chat-message",
92
+ "data-slot": "pending",
93
+ className: cn("inline-flex items-center", className),
94
+ ...props,
95
+ children: [
96
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true" }),
97
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true" }),
98
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true" })
99
+ ]
100
+ });
101
+ }
85
102
  function ChatMessageActions({ className, ...props }) {
86
103
  return /* @__PURE__ */ jsx("div", {
87
104
  "data-control-ui": "chat-message",
@@ -92,4 +109,4 @@ function ChatMessageActions({ className, ...props }) {
92
109
  });
93
110
  }
94
111
  //#endregion
95
- export { ChatMessage, ChatMessageActions, ChatMessageAvatar, ChatMessageBody, ChatMessageContent, ChatMessageHeader, ChatMessageRow };
112
+ export { ChatMessage, ChatMessageActions, ChatMessageAvatar, ChatMessageBody, ChatMessageContent, ChatMessageHeader, ChatMessagePending, ChatMessageRow };
package/dist/context.js CHANGED
@@ -56,7 +56,6 @@ function ContextTrigger({ "aria-label": ariaLabel, variant = "surface", size = "
56
56
  "data-status": model.status,
57
57
  className: "relative inline-flex",
58
58
  children: /* @__PURE__ */ jsx(Button, {
59
- "data-context-trigger-button": "true",
60
59
  ...props,
61
60
  render: /* @__PURE__ */ jsx(PopoverTrigger, {}),
62
61
  variant,
@@ -97,7 +96,6 @@ function ContextTrigger({ "aria-label": ariaLabel, variant = "surface", size = "
97
96
  "data-control-ui": "context",
98
97
  "data-control-family": "context",
99
98
  "data-slot": "trigger-label",
100
- className: "overflow-hidden",
101
99
  children: shortLabel
102
100
  })] })
103
101
  })
@@ -0,0 +1,60 @@
1
+ import { EmailColorScheme } from "./theme.js";
2
+ import { EmailFooterPlacement } from "./email.js";
3
+ import { EmailFooterAlign, EmailNavLink, EmailSocialLink } from "./email-footer.js";
4
+ import { ReactNode } from "react";
5
+ //#region src/email/email-brand.d.ts
6
+ export type EmailBrand = {
7
+ name: string;
8
+ homeUrl: string;
9
+ logoUrl?: string;
10
+ logoDarkUrl?: string;
11
+ logoWidth?: number;
12
+ logoHeight?: number;
13
+ };
14
+ export type EmailFooterContent = {
15
+ tagline: string;
16
+ socialLinks: EmailSocialLink[];
17
+ socialIconBaseUrl?: string;
18
+ helpLinks: EmailNavLink[];
19
+ sender: {
20
+ company: string;
21
+ addressLines: string[];
22
+ };
23
+ reason: string;
24
+ legal: string;
25
+ align?: EmailFooterAlign;
26
+ placement?: EmailFooterPlacement;
27
+ } & ({
28
+ mailing: "transactional";
29
+ } | {
30
+ mailing: "marketing";
31
+ unsubscribeUrl: string;
32
+ preferencesUrl?: string;
33
+ });
34
+ export declare function EmailLogo({ name, src, width, height, href }: {
35
+ name: string;
36
+ src?: string;
37
+ width?: number;
38
+ height?: number;
39
+ href?: string;
40
+ }): import("react").JSX.Element;
41
+ export declare function EmailBrowserLink({ href, children }: {
42
+ href: string;
43
+ children?: ReactNode;
44
+ }): import("react").JSX.Element;
45
+ export type EmailHeaderAlign = "split" | "center";
46
+ export declare function EmailHeader({ children, aside, align }: {
47
+ children: ReactNode;
48
+ aside?: ReactNode;
49
+ align?: EmailHeaderAlign;
50
+ }): import("react").JSX.Element;
51
+ export declare function EmailBrandHeader({ brand, browserUrl, colorScheme, align }: {
52
+ brand: EmailBrand;
53
+ browserUrl?: string;
54
+ colorScheme?: EmailColorScheme;
55
+ align?: EmailHeaderAlign;
56
+ }): import("react").JSX.Element;
57
+ export declare function EmailBrandFooter({ footer }: {
58
+ footer: EmailFooterContent;
59
+ }): import("react").JSX.Element;
60
+ //#endregion
@@ -0,0 +1,101 @@
1
+ import { EmailCaption, EmailMutedLink, EmailSection } from "./email.js";
2
+ import { EmailAddress, EmailFooter, EmailFooterLegal, EmailFooterLinks, EmailSocialLinks, EmailUnsubscribe } from "./email-footer.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { Column, Img, Link, Row } from "react-email";
5
+ //#region src/email/email-brand.tsx
6
+ function EmailLogo({ name, src, width = 120, height = 32, href }) {
7
+ const mark = src ? /* @__PURE__ */ jsx(Img, {
8
+ src,
9
+ alt: name,
10
+ width,
11
+ height,
12
+ className: "inline-block"
13
+ }) : /* @__PURE__ */ jsx("span", {
14
+ className: "font-display text-heading-4 font-semibold text-card-foreground",
15
+ children: name
16
+ });
17
+ return href ? /* @__PURE__ */ jsx(Link, {
18
+ href,
19
+ className: "text-card-foreground no-underline",
20
+ children: mark
21
+ }) : mark;
22
+ }
23
+ function EmailBrowserLink({ href, children = "View in browser" }) {
24
+ return /* @__PURE__ */ jsx(EmailMutedLink, {
25
+ href,
26
+ children
27
+ });
28
+ }
29
+ function EmailHeader({ children, aside, align = "split" }) {
30
+ if (align === "center") return /* @__PURE__ */ jsxs(EmailSection, {
31
+ align: "center",
32
+ className: "mb-8",
33
+ children: [children, aside ? /* @__PURE__ */ jsx(EmailSection, {
34
+ align: "center",
35
+ className: "mt-3",
36
+ children: aside
37
+ }) : null]
38
+ });
39
+ return /* @__PURE__ */ jsxs(Row, {
40
+ className: "mb-8",
41
+ children: [/* @__PURE__ */ jsx(Column, {
42
+ className: "align-middle",
43
+ children
44
+ }), aside ? /* @__PURE__ */ jsx(Column, {
45
+ align: "right",
46
+ className: "align-middle",
47
+ children: aside
48
+ }) : null]
49
+ });
50
+ }
51
+ function EmailBrandHeader({ brand, browserUrl, colorScheme = "light", align = "split" }) {
52
+ const logoUrl = colorScheme === "dark" ? brand.logoDarkUrl ?? brand.logoUrl : brand.logoUrl;
53
+ return /* @__PURE__ */ jsx(EmailHeader, {
54
+ align,
55
+ aside: browserUrl ? /* @__PURE__ */ jsx(EmailBrowserLink, { href: browserUrl }) : null,
56
+ children: /* @__PURE__ */ jsx(EmailLogo, {
57
+ name: brand.name,
58
+ src: logoUrl,
59
+ width: brand.logoWidth,
60
+ height: brand.logoHeight,
61
+ href: brand.homeUrl
62
+ })
63
+ });
64
+ }
65
+ function EmailBrandFooter({ footer }) {
66
+ return /* @__PURE__ */ jsxs(EmailFooter, {
67
+ align: footer.align,
68
+ placement: footer.placement,
69
+ children: [
70
+ /* @__PURE__ */ jsx(EmailSocialLinks, {
71
+ links: footer.socialLinks,
72
+ iconBaseUrl: footer.socialIconBaseUrl
73
+ }),
74
+ /* @__PURE__ */ jsx(EmailFooterLinks, { links: footer.helpLinks }),
75
+ /* @__PURE__ */ jsx(EmailCaption, {
76
+ className: "mb-0",
77
+ children: footer.tagline
78
+ }),
79
+ /* @__PURE__ */ jsxs(EmailFooterLegal, { children: [
80
+ /* @__PURE__ */ jsx(EmailAddress, {
81
+ company: footer.sender.company,
82
+ lines: footer.sender.addressLines
83
+ }),
84
+ /* @__PURE__ */ jsx(EmailCaption, {
85
+ className: "mb-2",
86
+ children: footer.reason
87
+ }),
88
+ footer.mailing === "marketing" ? /* @__PURE__ */ jsx(EmailUnsubscribe, {
89
+ unsubscribeUrl: footer.unsubscribeUrl,
90
+ preferencesUrl: footer.preferencesUrl
91
+ }) : null,
92
+ /* @__PURE__ */ jsx(EmailCaption, {
93
+ className: "mb-0",
94
+ children: footer.legal
95
+ })
96
+ ] })
97
+ ]
98
+ });
99
+ }
100
+ //#endregion
101
+ export { EmailBrandFooter, EmailBrandHeader, EmailBrowserLink, EmailHeader, EmailLogo };
@@ -0,0 +1,18 @@
1
+ import { EmailTheme } from "./theme.js";
2
+ import { EmailVariant } from "./email.js";
3
+ import { CodeInlineProps, PrismLanguage } from "react-email";
4
+ //#region src/email/email-code.d.ts
5
+ export declare function EmailInlineCode({ className, ...props }: CodeInlineProps): import("react").JSX.Element;
6
+ export declare function EmailCodeBlock({ theme, code, language, lineNumbers, className }: {
7
+ theme: EmailTheme;
8
+ code: string;
9
+ language: PrismLanguage;
10
+ lineNumbers?: boolean;
11
+ className?: string;
12
+ }): import("react").JSX.Element;
13
+ export declare function EmailMarkdown({ theme, variant, children }: {
14
+ theme: EmailTheme;
15
+ variant?: EmailVariant;
16
+ children: string;
17
+ }): import("react").JSX.Element;
18
+ //#endregion
@@ -0,0 +1,172 @@
1
+ import { surfaceColors } from "./email.js";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { CodeBlock, CodeInline, Markdown, Section } from "react-email";
4
+ //#region src/email/email-code.tsx
5
+ function EmailInlineCode({ className = "", ...props }) {
6
+ return /* @__PURE__ */ jsx(CodeInline, {
7
+ ...props,
8
+ className: `rounded-control bg-muted px-1 font-mono text-card-foreground ${className}`
9
+ });
10
+ }
11
+ function prismTheme(theme) {
12
+ const { code } = theme;
13
+ return {
14
+ base: {
15
+ margin: "0",
16
+ padding: "16px",
17
+ background: theme.colors.muted,
18
+ color: code.foreground,
19
+ borderRadius: theme.radii.panel,
20
+ fontFamily: theme.fonts.mono,
21
+ fontSize: theme.text.caption.fontSize,
22
+ lineHeight: "1.6",
23
+ whiteSpace: "pre-wrap",
24
+ wordBreak: "break-word"
25
+ },
26
+ comment: { color: code.comment },
27
+ prolog: { color: code.comment },
28
+ doctype: { color: code.comment },
29
+ cdata: { color: code.comment },
30
+ punctuation: { color: code.punctuation },
31
+ operator: { color: code.punctuation },
32
+ keyword: { color: code.keyword },
33
+ atrule: { color: code.keyword },
34
+ "attr-value": { color: code.string },
35
+ string: { color: code.string },
36
+ char: { color: code.string },
37
+ inserted: { color: code.string },
38
+ regex: { color: code.string },
39
+ "attr-name": { color: code.parameter },
40
+ property: { color: code.parameter },
41
+ variable: { color: code.parameter },
42
+ function: { color: code.function },
43
+ "class-name": { color: code.function },
44
+ tag: { color: code.function },
45
+ boolean: { color: code.constant },
46
+ number: { color: code.constant },
47
+ constant: { color: code.constant },
48
+ symbol: { color: code.constant },
49
+ deleted: { color: code.constant },
50
+ builtin: { color: code.constant }
51
+ };
52
+ }
53
+ function EmailCodeBlock({ theme, code, language, lineNumbers = false, className = "" }) {
54
+ return /* @__PURE__ */ jsx(Section, {
55
+ className: `mb-6 ${className}`,
56
+ children: /* @__PURE__ */ jsx(CodeBlock, {
57
+ code,
58
+ language,
59
+ lineNumbers,
60
+ fontFamily: theme.fonts.mono,
61
+ theme: prismTheme(theme)
62
+ })
63
+ });
64
+ }
65
+ function markdownStyles(theme, variant) {
66
+ const colors = surfaceColors(theme, variant);
67
+ const body = {
68
+ margin: "0 0 16px",
69
+ fontFamily: theme.fonts.body,
70
+ fontSize: theme.text.body.fontSize,
71
+ lineHeight: theme.text.body.lineHeight,
72
+ color: colors["card-foreground"]
73
+ };
74
+ function headingStyle(size) {
75
+ const style = theme.text[size];
76
+ return {
77
+ margin: "0 0 16px",
78
+ fontFamily: theme.fonts.display,
79
+ fontSize: style.fontSize,
80
+ lineHeight: style.lineHeight,
81
+ fontWeight: style.fontWeight,
82
+ letterSpacing: style.letterSpacing,
83
+ color: colors["card-foreground"]
84
+ };
85
+ }
86
+ const monospace = {
87
+ fontFamily: theme.fonts.mono,
88
+ fontSize: theme.text.caption.fontSize,
89
+ background: theme.colors.muted,
90
+ color: theme.code.foreground,
91
+ borderRadius: theme.radii.control
92
+ };
93
+ return {
94
+ p: body,
95
+ li: {
96
+ ...body,
97
+ margin: "0 0 4px"
98
+ },
99
+ ul: {
100
+ ...body,
101
+ paddingLeft: "20px"
102
+ },
103
+ ol: {
104
+ ...body,
105
+ paddingLeft: "20px"
106
+ },
107
+ h1: headingStyle("heading-1"),
108
+ h2: headingStyle("heading-2"),
109
+ h3: headingStyle("heading-3"),
110
+ h4: headingStyle("heading-4"),
111
+ h5: headingStyle("heading-4"),
112
+ h6: headingStyle("heading-4"),
113
+ bold: { fontWeight: 600 },
114
+ italic: { fontStyle: "italic" },
115
+ link: {
116
+ color: colors["primary-text"],
117
+ textDecoration: "underline"
118
+ },
119
+ codeInline: {
120
+ ...monospace,
121
+ padding: "2px 4px"
122
+ },
123
+ codeBlock: {
124
+ ...monospace,
125
+ display: "block",
126
+ padding: "16px",
127
+ borderRadius: theme.radii.panel,
128
+ whiteSpace: "pre-wrap"
129
+ },
130
+ blockQuote: {
131
+ ...body,
132
+ borderLeft: `3px solid ${theme.colors.border}`,
133
+ paddingLeft: "16px",
134
+ color: colors["muted-foreground"]
135
+ },
136
+ hr: {
137
+ border: "none",
138
+ borderTop: `1px solid ${theme.colors.border}`,
139
+ margin: "24px 0"
140
+ },
141
+ image: {
142
+ maxWidth: "100%",
143
+ borderRadius: theme.radii.scene
144
+ },
145
+ table: {
146
+ width: "100%",
147
+ borderCollapse: "collapse",
148
+ marginBottom: "16px"
149
+ },
150
+ th: {
151
+ ...body,
152
+ margin: "0",
153
+ textAlign: "left",
154
+ padding: "8px 0",
155
+ borderBottom: `1px solid ${theme.colors.border}`
156
+ },
157
+ td: {
158
+ ...body,
159
+ margin: "0",
160
+ padding: "8px 0",
161
+ borderBottom: `1px solid ${theme.colors.border}`
162
+ }
163
+ };
164
+ }
165
+ function EmailMarkdown({ theme, variant = "contained", children }) {
166
+ return /* @__PURE__ */ jsx(Markdown, {
167
+ markdownCustomStyles: markdownStyles(theme, variant),
168
+ children
169
+ });
170
+ }
171
+ //#endregion
172
+ export { EmailCodeBlock, EmailInlineCode, EmailMarkdown };
@@ -0,0 +1,63 @@
1
+ import { EmailFooterPlacement } from "./email.js";
2
+ import { ReactNode } from "react";
3
+ //#region src/email/email-footer.d.ts
4
+ export type EmailNavLink = {
5
+ label: string;
6
+ href: string;
7
+ };
8
+ export declare const emailSocialPlatformNames: {
9
+ readonly x: "X";
10
+ readonly bluesky: "Bluesky";
11
+ readonly threads: "Threads";
12
+ readonly instagram: "Instagram";
13
+ readonly facebook: "Facebook";
14
+ readonly linkedin: "LinkedIn";
15
+ readonly youtube: "YouTube";
16
+ readonly tiktok: "TikTok";
17
+ readonly github: "GitHub";
18
+ readonly discord: "Discord";
19
+ readonly slack: "Slack";
20
+ readonly mastodon: "Mastodon";
21
+ };
22
+ export type EmailSocialPlatform = keyof typeof emailSocialPlatformNames;
23
+ export type EmailSocialLink = {
24
+ href: string;
25
+ iconAlt?: string;
26
+ } & ({
27
+ platform: EmailSocialPlatform;
28
+ label?: string;
29
+ iconUrl?: string;
30
+ } | {
31
+ platform?: undefined;
32
+ label: string;
33
+ iconUrl?: string;
34
+ });
35
+ export type EmailFooterAlign = "start" | "center";
36
+ export declare function EmailFooter({ children, align, placement }: {
37
+ children: ReactNode;
38
+ align?: EmailFooterAlign;
39
+ placement?: EmailFooterPlacement;
40
+ }): import("react").JSX.Element;
41
+ export declare function EmailFooterLegal({ children, className }: {
42
+ children: ReactNode;
43
+ className?: string;
44
+ }): import("react").JSX.Element;
45
+ export declare function EmailSocialLinks({ links, iconBaseUrl, iconSize }: {
46
+ links: EmailSocialLink[];
47
+ iconBaseUrl?: string;
48
+ iconSize?: number;
49
+ }): import("react").JSX.Element | null;
50
+ export declare function EmailFooterLinks({ links }: {
51
+ links: EmailNavLink[];
52
+ }): import("react").JSX.Element | null;
53
+ export declare function EmailAddress({ company, lines }: {
54
+ company: string;
55
+ lines: string[];
56
+ }): import("react").JSX.Element;
57
+ export declare function EmailUnsubscribe({ unsubscribeUrl, preferencesUrl, unsubscribeLabel, preferencesLabel }: {
58
+ unsubscribeUrl: string;
59
+ preferencesUrl?: string;
60
+ unsubscribeLabel?: string;
61
+ preferencesLabel?: string;
62
+ }): import("react").JSX.Element;
63
+ //#endregion