@ctrl-ui/react 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +1 -0
  2. package/dist/blocks/coding-agent.js +3 -1
  3. package/dist/blocks/file-explorer.js +3 -1
  4. package/dist/blocks/settings.js +3 -1
  5. package/dist/control-props.d.ts +2 -1
  6. package/dist/email/email.d.ts +28 -0
  7. package/dist/email/email.js +70 -0
  8. package/dist/email/templates.d.ts +51 -0
  9. package/dist/email/templates.js +228 -0
  10. package/dist/email/theme.d.ts +23 -0
  11. package/dist/email/theme.js +98 -0
  12. package/dist/extensions/create-track-highlight.d.ts +0 -2
  13. package/dist/extensions/create-track-highlight.js +49 -8
  14. package/dist/extensions/supports-anchor-transitions.d.ts +4 -0
  15. package/dist/extensions/supports-anchor-transitions.js +31 -0
  16. package/dist/extensions/track-highlight.d.ts +0 -6
  17. package/dist/extensions/track-highlight.js +25 -9
  18. package/dist/knob-contracts/button-knobs.d.ts +1 -1
  19. package/dist/knob-contracts/button-knobs.js +4 -1
  20. package/dist/knob-contracts/progressive-blur-knobs.d.ts +5 -0
  21. package/dist/knob-contracts/progressive-blur-knobs.js +9 -0
  22. package/dist/scripts/contrast-eval.d.ts +46 -0
  23. package/dist/scripts/contrast-eval.js +327 -0
  24. package/dist/skin.config.js +9 -1
  25. package/dist/skin.d.ts +11 -9
  26. package/dist/skin.js +0 -1
  27. package/dist/styles/index.css +2 -1
  28. package/dist/styles/recipes/button-group.css +46 -0
  29. package/dist/styles/recipes/button.css +53 -5
  30. package/dist/styles/recipes/progressive-blur.css +145 -0
  31. package/dist/styles/recipes/scroll-area.css +140 -5
  32. package/dist/styles/recipes/sidebar.css +49 -0
  33. package/dist/styles/recipes/table-of-contents.css +1 -1
  34. package/dist/styles/recipes/track-highlight.css +126 -2
  35. package/dist/styles/recipes/tree.css +4 -0
  36. package/dist/ui/button-group.d.ts +3 -1
  37. package/dist/ui/button-group.js +21 -18
  38. package/dist/ui/button.d.ts +3 -1
  39. package/dist/ui/button.js +13 -4
  40. package/dist/ui/checkbox-group.d.ts +5 -3
  41. package/dist/ui/checkbox-group.js +23 -6
  42. package/dist/ui/color-picker.js +1 -1
  43. package/dist/ui/dropdown-menu.d.ts +2 -3
  44. package/dist/ui/dropdown-menu.js +1 -7
  45. package/dist/ui/progressive-blur.d.ts +12 -0
  46. package/dist/ui/progressive-blur.js +47 -0
  47. package/dist/ui/scroll-area.d.ts +4 -1
  48. package/dist/ui/scroll-area.js +27 -4
  49. package/dist/ui/sidebar-menu.d.ts +39 -0
  50. package/dist/ui/sidebar-menu.js +135 -0
  51. package/dist/ui/sidebar-provider.d.ts +28 -0
  52. package/dist/ui/sidebar-provider.js +80 -0
  53. package/dist/ui/sidebar.d.ts +4 -61
  54. package/dist/ui/sidebar.js +23 -202
  55. package/dist/ui/table-of-contents.js +3 -11
  56. package/dist/ui/toggle.d.ts +3 -2
  57. package/dist/ui/toggle.js +22 -10
  58. package/dist/ui/tree.js +13 -19
  59. package/package.json +5 -1
package/README.md CHANGED
@@ -68,3 +68,4 @@ Install a peer only when you import the component that needs it.
68
68
  | `ui/phone-input` | `react-phone-number-input`, `libphonenumber-js`, `zod` |
69
69
  | `ui/resizable` | `react-resizable-panels` |
70
70
  | `chat-composer` | `prosemirror-*` |
71
+ | `email/email`, `email/templates` | `react-email` |
@@ -3,7 +3,9 @@ import { cn } from "../lib/cn.js";
3
3
  import { Button } from "../ui/button.js";
4
4
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible.js";
5
5
  import { ChatLayout, ChatThread } from "../chat-layout.js";
6
- import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarRail, SidebarTrigger, useSidebar } from "../ui/sidebar.js";
6
+ import { SidebarProvider, useSidebar } from "../ui/sidebar-provider.js";
7
+ import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar-menu.js";
8
+ import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarInset, SidebarRail, SidebarTrigger } from "../ui/sidebar.js";
7
9
  import { jsx, jsxs } from "react/jsx-runtime";
8
10
  import { BotIcon, ChevronRightIcon, CircleIcon, FolderIcon, PlusIcon, SearchIcon } from "lucide-react";
9
11
  //#region src/blocks/coding-agent.tsx
@@ -3,7 +3,9 @@ import { cn } from "../lib/cn.js";
3
3
  import { Button } from "../ui/button.js";
4
4
  import { ScrollArea } from "../ui/scroll-area.js";
5
5
  import { InputGroup, InputGroupAddon, InputGroupInput } from "../ui/input-group.js";
6
- import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarRail, SidebarTrigger, useSidebar } from "../ui/sidebar.js";
6
+ import { SidebarProvider, useSidebar } from "../ui/sidebar-provider.js";
7
+ import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar-menu.js";
8
+ import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarInset, SidebarRail, SidebarTrigger } from "../ui/sidebar.js";
7
9
  import { groupFileExplorerItems, resolveFileExplorer, searchFileExplorer } from "./file-explorer-data.js";
8
10
  import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "../ui/empty.js";
9
11
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "../ui/resizable.js";
@@ -3,7 +3,9 @@ import { cn } from "../lib/cn.js";
3
3
  import { Button } from "../ui/button.js";
4
4
  import { ScrollArea } from "../ui/scroll-area.js";
5
5
  import { InputGroup, InputGroupAddon, InputGroupInput } from "../ui/input-group.js";
6
- import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger, useSidebar } from "../ui/sidebar.js";
6
+ import { SidebarProvider, useSidebar } from "../ui/sidebar-provider.js";
7
+ import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar-menu.js";
8
+ import { Sidebar, SidebarContent, SidebarHeader, SidebarInset, SidebarTrigger } from "../ui/sidebar.js";
7
9
  import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "../ui/empty.js";
8
10
  import { searchSettings } from "./settings-data.js";
9
11
  import { Field, FieldContent, FieldDescription, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet } from "../ui/field.js";
@@ -23,6 +23,7 @@ type OpenChangeEventDetails = {
23
23
  trigger: Element | undefined;
24
24
  };
25
25
  type SelectionIndicator = "none" | "slide";
26
+ type HoverIndicator = "none" | "hover";
26
27
  declare const SIDEBAR_COOKIE_NAME = "sidebar_state";
27
28
  //#endregion
28
- export { ControlledChoice, ControlledMultiChoice, FormSubmitEvent, OpenChangeEventDetails, OpenChangeReason, RenderProp, SIDEBAR_COOKIE_NAME, SelectionIndicator };
29
+ export { ControlledChoice, ControlledMultiChoice, FormSubmitEvent, HoverIndicator, OpenChangeEventDetails, OpenChangeReason, RenderProp, SIDEBAR_COOKIE_NAME, SelectionIndicator };
@@ -0,0 +1,28 @@
1
+ import { EmailTheme } from "./theme.js";
2
+ import { ReactNode } from "react";
3
+ import { ButtonProps, HeadingProps, LinkProps, TextProps } from "react-email";
4
+ //#region src/email/email.d.ts
5
+ declare function EmailLayout({ theme, preview, children, head, lang, dir }: {
6
+ theme: EmailTheme;
7
+ preview: string;
8
+ children: ReactNode;
9
+ head?: ReactNode;
10
+ lang?: string;
11
+ dir?: "ltr" | "rtl";
12
+ }): import("react").JSX.Element;
13
+ declare const headingClasses: {
14
+ h1: string;
15
+ h2: string;
16
+ h3: string;
17
+ h4: string;
18
+ };
19
+ declare function EmailHeading({ as, className, ...props }: Omit<HeadingProps, "as"> & {
20
+ as?: keyof typeof headingClasses;
21
+ }): import("react").JSX.Element;
22
+ declare function EmailText({ tone, className, style, ...props }: TextProps & {
23
+ tone?: "default" | "muted";
24
+ }): import("react").JSX.Element;
25
+ declare function EmailButton({ className, ...props }: ButtonProps): import("react").JSX.Element;
26
+ declare function EmailLink({ className, ...props }: LinkProps): import("react").JSX.Element;
27
+ //#endregion
28
+ export { EmailButton, EmailHeading, EmailLayout, EmailLink, EmailText };
@@ -0,0 +1,70 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Body, Button, Container, Head, Heading, Html, Link, Preview, Tailwind, Text, pixelBasedPreset } from "react-email";
3
+ //#region src/email/email.tsx
4
+ function tailwindTheme(theme) {
5
+ return {
6
+ presets: [pixelBasedPreset],
7
+ theme: { extend: {
8
+ colors: theme.colors,
9
+ fontFamily: {
10
+ body: theme.fonts.body,
11
+ display: theme.fonts.display
12
+ },
13
+ fontSize: Object.fromEntries(Object.entries(theme.text).map(([name, { fontSize, ...options }]) => [name, [fontSize, options]]))
14
+ } }
15
+ };
16
+ }
17
+ function EmailLayout({ theme, preview, children, head, lang = "en", dir = "ltr" }) {
18
+ return /* @__PURE__ */ jsx(Html, {
19
+ lang,
20
+ dir,
21
+ children: /* @__PURE__ */ jsxs(Tailwind, {
22
+ config: tailwindTheme(theme),
23
+ children: [/* @__PURE__ */ jsx(Head, { children: head }), /* @__PURE__ */ jsxs(Body, {
24
+ className: "m-0 bg-background p-4 font-body text-body text-foreground",
25
+ children: [/* @__PURE__ */ jsx(Preview, { children: preview }), /* @__PURE__ */ jsx(Container, {
26
+ className: "mx-auto w-full max-w-[600px] bg-card p-6 text-card-foreground",
27
+ children
28
+ })]
29
+ })]
30
+ })
31
+ });
32
+ }
33
+ const headingClasses = {
34
+ h1: "text-heading-1",
35
+ h2: "text-heading-2",
36
+ h3: "text-heading-3",
37
+ h4: "text-heading-4"
38
+ };
39
+ function EmailHeading({ as = "h1", className = "", ...props }) {
40
+ return /* @__PURE__ */ jsx(Heading, {
41
+ ...props,
42
+ as,
43
+ className: `mb-4 mt-0 font-display text-card-foreground ${headingClasses[as]} ${className}`
44
+ });
45
+ }
46
+ function EmailText({ tone = "default", className = "", style, ...props }) {
47
+ const textColor = tone === "muted" ? "text-muted-foreground" : "text-card-foreground";
48
+ return /* @__PURE__ */ jsx(Text, {
49
+ ...props,
50
+ style: {
51
+ overflowWrap: "break-word",
52
+ ...style
53
+ },
54
+ className: `mb-4 mt-0 font-body text-body ${textColor} ${className}`
55
+ });
56
+ }
57
+ function EmailButton({ className = "", ...props }) {
58
+ return /* @__PURE__ */ jsx(Button, {
59
+ ...props,
60
+ className: `box-border bg-primary px-5 py-3 text-center font-body text-body font-semibold text-primary-foreground no-underline ${className}`
61
+ });
62
+ }
63
+ function EmailLink({ className = "", ...props }) {
64
+ return /* @__PURE__ */ jsx(Link, {
65
+ ...props,
66
+ className: `font-body text-primary-text underline ${className}`
67
+ });
68
+ }
69
+ //#endregion
70
+ export { EmailButton, EmailHeading, EmailLayout, EmailLink, EmailText };
@@ -0,0 +1,51 @@
1
+ import { EmailTheme } from "./theme.js";
2
+ //#region src/email/templates.d.ts
3
+ type EmailBrandProps = {
4
+ theme: EmailTheme;
5
+ brand: string;
6
+ footer: string;
7
+ };
8
+ declare function InvitationEmail({ theme, brand, footer, inviter, workspace, inviteUrl }: EmailBrandProps & {
9
+ inviter: string;
10
+ workspace: string;
11
+ inviteUrl: string;
12
+ }): import("react").JSX.Element;
13
+ declare function ProductEmail({ theme, brand, footer, title, description, imageUrl, imageAlt, actionUrl }: EmailBrandProps & {
14
+ title: string;
15
+ description: string;
16
+ imageUrl: string;
17
+ imageAlt: string;
18
+ actionUrl: string;
19
+ }): import("react").JSX.Element;
20
+ declare function EditorialEmail({ theme, brand, footer, title, description, imageUrl, imageAlt, articleUrl }: EmailBrandProps & {
21
+ title: string;
22
+ description: string;
23
+ imageUrl: string;
24
+ imageAlt: string;
25
+ articleUrl: string;
26
+ }): import("react").JSX.Element;
27
+ type EmailArticle = {
28
+ title: string;
29
+ description: string;
30
+ href: string;
31
+ imageUrl: string;
32
+ imageAlt: string;
33
+ };
34
+ declare function NewsletterEmail({ theme, brand, footer, articles, unsubscribeUrl }: EmailBrandProps & {
35
+ articles: EmailArticle[];
36
+ unsubscribeUrl: string;
37
+ }): import("react").JSX.Element;
38
+ declare function SummaryEmail({ theme, brand, footer, period, metrics, details, dashboardUrl }: EmailBrandProps & {
39
+ period: string;
40
+ metrics: {
41
+ label: string;
42
+ value: string;
43
+ }[];
44
+ details: {
45
+ label: string;
46
+ value: string;
47
+ }[];
48
+ dashboardUrl: string;
49
+ }): import("react").JSX.Element;
50
+ //#endregion
51
+ export { EditorialEmail, EmailArticle, InvitationEmail, NewsletterEmail, ProductEmail, SummaryEmail };
@@ -0,0 +1,228 @@
1
+ import { EmailButton, EmailHeading, EmailLayout, EmailLink, EmailText } from "./email.js";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { Column, Hr, Img, Row, Section } from "react-email";
4
+ //#region src/email/templates.tsx
5
+ function EmailFooter({ brand, children }) {
6
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
7
+ /* @__PURE__ */ jsx(Hr, { className: "my-6 border-0 border-t border-solid border-t-border" }),
8
+ /* @__PURE__ */ jsx(EmailText, {
9
+ tone: "muted",
10
+ className: "mb-1 text-caption",
11
+ children: brand
12
+ }),
13
+ /* @__PURE__ */ jsx(EmailText, {
14
+ tone: "muted",
15
+ className: "mb-0 text-caption",
16
+ children
17
+ })
18
+ ] });
19
+ }
20
+ function InvitationEmail({ theme, brand, footer, inviter, workspace, inviteUrl }) {
21
+ return /* @__PURE__ */ jsxs(EmailLayout, {
22
+ theme,
23
+ preview: `${inviter} invited you to ${workspace}`,
24
+ children: [
25
+ /* @__PURE__ */ jsx(EmailText, {
26
+ tone: "muted",
27
+ className: "mb-8 text-caption",
28
+ children: brand
29
+ }),
30
+ /* @__PURE__ */ jsx(EmailHeading, { children: "A place for your next idea." }),
31
+ /* @__PURE__ */ jsxs(EmailText, { children: [
32
+ inviter,
33
+ " invited you to join ",
34
+ /* @__PURE__ */ jsx("strong", { children: workspace }),
35
+ ". Bring your work, share a little inspiration, and make something together."
36
+ ] }),
37
+ /* @__PURE__ */ jsxs(Section, {
38
+ className: "my-6 bg-muted p-5",
39
+ children: [/* @__PURE__ */ jsx(EmailHeading, {
40
+ as: "h3",
41
+ children: "Your team is waiting"
42
+ }), /* @__PURE__ */ jsx(EmailText, {
43
+ className: "mb-0",
44
+ children: "Projects, conversations, and the details that move work forward. All in one shared space."
45
+ })]
46
+ }),
47
+ /* @__PURE__ */ jsxs(EmailButton, {
48
+ href: inviteUrl,
49
+ children: ["Join ", workspace]
50
+ }),
51
+ /* @__PURE__ */ jsx(EmailFooter, {
52
+ brand,
53
+ children: footer
54
+ })
55
+ ]
56
+ });
57
+ }
58
+ function ProductEmail({ theme, brand, footer, title, description, imageUrl, imageAlt, actionUrl }) {
59
+ return /* @__PURE__ */ jsxs(EmailLayout, {
60
+ theme,
61
+ preview: title,
62
+ children: [
63
+ /* @__PURE__ */ jsxs(EmailText, {
64
+ tone: "muted",
65
+ className: "mb-6 text-caption",
66
+ children: [brand, " / NEW RELEASE"]
67
+ }),
68
+ /* @__PURE__ */ jsx(Img, {
69
+ src: imageUrl,
70
+ alt: imageAlt,
71
+ width: "552",
72
+ className: "mb-6 block h-auto w-full"
73
+ }),
74
+ /* @__PURE__ */ jsx(EmailHeading, { children: title }),
75
+ /* @__PURE__ */ jsx(EmailText, { children: description }),
76
+ /* @__PURE__ */ jsx(EmailButton, {
77
+ href: actionUrl,
78
+ children: "Explore what’s new"
79
+ }),
80
+ /* @__PURE__ */ jsx(EmailFooter, {
81
+ brand,
82
+ children: footer
83
+ })
84
+ ]
85
+ });
86
+ }
87
+ function EditorialEmail({ theme, brand, footer, title, description, imageUrl, imageAlt, articleUrl }) {
88
+ return /* @__PURE__ */ jsxs(EmailLayout, {
89
+ theme,
90
+ preview: title,
91
+ children: [
92
+ /* @__PURE__ */ jsxs(EmailText, {
93
+ tone: "muted",
94
+ className: "mb-6 text-caption",
95
+ children: [brand, " / IN GOOD COMPANY"]
96
+ }),
97
+ /* @__PURE__ */ jsx(EmailHeading, { children: "Fresh perspectives." }),
98
+ /* @__PURE__ */ jsxs(Row, { children: [/* @__PURE__ */ jsx(Column, {
99
+ width: "36%",
100
+ className: "align-top",
101
+ children: /* @__PURE__ */ jsx(Img, {
102
+ src: imageUrl,
103
+ alt: imageAlt,
104
+ width: "198",
105
+ className: "block h-auto w-full"
106
+ })
107
+ }), /* @__PURE__ */ jsxs(Column, {
108
+ width: "64%",
109
+ className: "pl-5 align-top",
110
+ children: [
111
+ /* @__PURE__ */ jsx(EmailHeading, {
112
+ as: "h2",
113
+ children: title
114
+ }),
115
+ /* @__PURE__ */ jsx(EmailText, { children: description }),
116
+ /* @__PURE__ */ jsx(EmailLink, {
117
+ href: articleUrl,
118
+ children: "Read the story"
119
+ })
120
+ ]
121
+ })] }),
122
+ /* @__PURE__ */ jsx(EmailFooter, {
123
+ brand,
124
+ children: footer
125
+ })
126
+ ]
127
+ });
128
+ }
129
+ function NewsletterEmail({ theme, brand, footer, articles, unsubscribeUrl }) {
130
+ return /* @__PURE__ */ jsxs(EmailLayout, {
131
+ theme,
132
+ preview: "A few things worth making time for.",
133
+ children: [
134
+ /* @__PURE__ */ jsxs(EmailText, {
135
+ tone: "muted",
136
+ className: "mb-6 text-caption",
137
+ children: [brand, " / THE WEEKLY EDIT"]
138
+ }),
139
+ /* @__PURE__ */ jsx(EmailHeading, { children: "A little room for inspiration." }),
140
+ /* @__PURE__ */ jsx(EmailText, {
141
+ tone: "muted",
142
+ children: "A few things worth making time for. Stories, spaces, and ideas from our community."
143
+ }),
144
+ articles.map((article) => /* @__PURE__ */ jsxs(Section, {
145
+ className: "mb-6",
146
+ children: [
147
+ /* @__PURE__ */ jsx(Img, {
148
+ src: article.imageUrl,
149
+ alt: article.imageAlt,
150
+ width: "552",
151
+ className: "mb-4 block h-auto w-full"
152
+ }),
153
+ /* @__PURE__ */ jsx(EmailHeading, {
154
+ as: "h2",
155
+ children: article.title
156
+ }),
157
+ /* @__PURE__ */ jsx(EmailText, { children: article.description }),
158
+ /* @__PURE__ */ jsx(EmailLink, {
159
+ href: article.href,
160
+ children: "Read the story"
161
+ })
162
+ ]
163
+ }, article.href)),
164
+ /* @__PURE__ */ jsx(EmailFooter, {
165
+ brand,
166
+ children: footer
167
+ }),
168
+ /* @__PURE__ */ jsx(EmailLink, {
169
+ href: unsubscribeUrl,
170
+ className: "text-caption",
171
+ children: "Unsubscribe"
172
+ })
173
+ ]
174
+ });
175
+ }
176
+ function SummaryEmail({ theme, brand, footer, period, metrics, details, dashboardUrl }) {
177
+ return /* @__PURE__ */ jsxs(EmailLayout, {
178
+ theme,
179
+ preview: `Your team’s progress · ${period}`,
180
+ children: [
181
+ /* @__PURE__ */ jsxs(EmailText, {
182
+ tone: "muted",
183
+ className: "mb-6 text-caption",
184
+ children: [
185
+ brand,
186
+ " / ",
187
+ period
188
+ ]
189
+ }),
190
+ /* @__PURE__ */ jsx(EmailHeading, { children: "Good work adds up." }),
191
+ /* @__PURE__ */ jsx(EmailText, { children: "Here’s what your team moved forward this week." }),
192
+ /* @__PURE__ */ jsx(Section, {
193
+ className: "my-6 bg-muted p-4",
194
+ children: /* @__PURE__ */ jsx(Row, {
195
+ className: "table-fixed",
196
+ children: metrics.map((metric) => /* @__PURE__ */ jsxs(Column, {
197
+ className: "px-2 align-top",
198
+ children: [/* @__PURE__ */ jsx(EmailText, {
199
+ className: "mb-1 text-heading-2 font-semibold",
200
+ children: metric.value
201
+ }), /* @__PURE__ */ jsx(EmailText, {
202
+ tone: "muted",
203
+ className: "mb-0 text-caption",
204
+ children: metric.label
205
+ })]
206
+ }, metric.label))
207
+ })
208
+ }),
209
+ details.map((detail) => /* @__PURE__ */ jsxs(Row, {
210
+ className: "table-fixed",
211
+ children: [/* @__PURE__ */ jsx(Column, { children: /* @__PURE__ */ jsx(EmailText, { children: detail.label }) }), /* @__PURE__ */ jsx(Column, {
212
+ align: "right",
213
+ children: /* @__PURE__ */ jsx(EmailText, { children: detail.value })
214
+ })]
215
+ }, detail.label)),
216
+ /* @__PURE__ */ jsx(EmailButton, {
217
+ href: dashboardUrl,
218
+ children: "View your workspace"
219
+ }),
220
+ /* @__PURE__ */ jsx(EmailFooter, {
221
+ brand,
222
+ children: footer
223
+ })
224
+ ]
225
+ });
226
+ }
227
+ //#endregion
228
+ export { EditorialEmail, InvitationEmail, NewsletterEmail, ProductEmail, SummaryEmail };
@@ -0,0 +1,23 @@
1
+ //#region src/email/theme.d.ts
2
+ declare const emailColors: readonly ["background", "foreground", "card", "card-foreground", "primary", "primary-foreground", "primary-text", "muted", "muted-foreground", "border"];
3
+ declare const headingSizes: readonly ["heading-1", "heading-2", "heading-3", "heading-4"];
4
+ type EmailColor = (typeof emailColors)[number];
5
+ type EmailHeadingSize = (typeof headingSizes)[number];
6
+ type EmailTypeStyle = {
7
+ fontSize: string;
8
+ lineHeight: string;
9
+ fontWeight: string;
10
+ letterSpacing: string;
11
+ };
12
+ type EmailTheme = {
13
+ colors: Record<EmailColor, string>;
14
+ fonts: {
15
+ body: string;
16
+ display: string;
17
+ };
18
+ text: Record<EmailHeadingSize | "body" | "caption", EmailTypeStyle>;
19
+ };
20
+ declare function createEmailTheme(tokens: ReadonlyMap<string, string>, rootFontSize?: number): EmailTheme;
21
+ declare function emailThemeFromCss(cssSources: string[], mode?: "light" | "dark", rootFontSize?: number): EmailTheme;
22
+ //#endregion
23
+ export { EmailTheme, createEmailTheme, emailThemeFromCss };
@@ -0,0 +1,98 @@
1
+ import { composite, resolveColor, tokenMaps } from "../scripts/contrast-eval.js";
2
+ //#region src/email/theme.ts
3
+ function resolveVariables(value, tokens, chain = []) {
4
+ if (chain.length > 16) throw new Error(`Email theme: token chain exceeds 16 references (${chain.join(" → ")}).`);
5
+ return value.replace(/var\(\s*(--[\w-]+)\s*(?:,\s*((?:[^()]|\([^()]*\))*))?\)/g, (_, name, fallback) => {
6
+ if (chain.includes(name)) throw new Error(`Email theme: circular reference to ${name}.`);
7
+ const resolved = tokens.get(name) ?? fallback;
8
+ if (resolved === void 0) throw new Error(`Email theme: missing ${name}.`);
9
+ return resolveVariables(resolved, tokens, [...chain, name]);
10
+ });
11
+ }
12
+ function tokenValue(name, tokens, fallback) {
13
+ const value = tokens.get(name) ?? fallback;
14
+ if (value === void 0) throw new Error(`Email theme: missing ${name}. Include core theme.css before the skin theme.`);
15
+ const resolved = resolveVariables(value, tokens, [name]).trim();
16
+ if (!resolved || /var\(|[;{}<>]/.test(resolved)) throw new Error(`Email theme: unsupported ${name}: ${value}.`);
17
+ return resolved;
18
+ }
19
+ function pixelSize(name, tokens, rootFontSize) {
20
+ const value = tokenValue(name, tokens);
21
+ const match = /^(\d*\.?\d+)(px|rem)$/.exec(value);
22
+ if (!match) throw new Error(`Email theme: ${name} must resolve to px or rem, received ${value}.`);
23
+ const pixels = Number(match[1]) * (match[2] === "rem" ? rootFontSize : 1);
24
+ if (pixels <= 0) throw new Error(`Email theme: ${name} must be positive.`);
25
+ return `${pixels}px`;
26
+ }
27
+ function numericStyle(name, tokens, fallback) {
28
+ const value = tokenValue(name, tokens, fallback);
29
+ if (!/^\d*\.?\d+(px|em)?$/.test(value)) throw new Error(`Email theme: unsupported ${name}: ${value}.`);
30
+ return value;
31
+ }
32
+ function createEmailTheme(tokens, rootFontSize = 16) {
33
+ if (!Number.isFinite(rootFontSize) || rootFontSize <= 0) throw new Error("Email theme: rootFontSize must be a positive pixel value.");
34
+ function color(name) {
35
+ const result = resolveColor(`var(--${name})`, tokens);
36
+ if ("unresolved" in result) throw new Error(`Email theme: --${name}: ${result.unresolved}.`);
37
+ return result;
38
+ }
39
+ const background = color("background");
40
+ if (background.alpha !== 1) throw new Error("Email theme: --background must be opaque.");
41
+ const card = composite(color("card"), background);
42
+ const primary = composite(color("primary"), card);
43
+ function opaqueColor(name, surface = card) {
44
+ const result = composite(color(name), surface);
45
+ return `rgb(${Math.round(result.r)}, ${Math.round(result.g)}, ${Math.round(result.b)})`;
46
+ }
47
+ function heading(name) {
48
+ const spacing = tokenValue(`--text-${name}--letter-spacing`, tokens, "0");
49
+ if (!/^-?\d*\.?\d+(px|em)?$/.test(spacing)) throw new Error(`Email theme: unsupported ${name} letter spacing: ${spacing}.`);
50
+ return {
51
+ fontSize: pixelSize(`--text-${name}`, tokens, rootFontSize),
52
+ lineHeight: numericStyle(`--text-${name}--line-height`, tokens, "1.3"),
53
+ fontWeight: numericStyle(`--text-${name}--font-weight`, tokens, "600"),
54
+ letterSpacing: spacing
55
+ };
56
+ }
57
+ return {
58
+ colors: {
59
+ background: opaqueColor("background", background),
60
+ foreground: opaqueColor("foreground", background),
61
+ card: opaqueColor("card", background),
62
+ "card-foreground": opaqueColor("card-foreground"),
63
+ primary: opaqueColor("primary"),
64
+ "primary-foreground": opaqueColor("primary-foreground", primary),
65
+ "primary-text": opaqueColor("primary-text"),
66
+ muted: opaqueColor("muted"),
67
+ "muted-foreground": opaqueColor("muted-foreground"),
68
+ border: opaqueColor("border")
69
+ },
70
+ fonts: {
71
+ body: tokenValue("--font-body", tokens),
72
+ display: tokenValue("--font-display", tokens)
73
+ },
74
+ text: {
75
+ body: {
76
+ fontSize: pixelSize("--text-body-lg", tokens, rootFontSize),
77
+ lineHeight: "1.6",
78
+ fontWeight: "400",
79
+ letterSpacing: "0"
80
+ },
81
+ caption: {
82
+ fontSize: pixelSize("--text-body", tokens, rootFontSize),
83
+ lineHeight: "1.5",
84
+ fontWeight: "400",
85
+ letterSpacing: "0"
86
+ },
87
+ "heading-1": heading("heading-1"),
88
+ "heading-2": heading("heading-2"),
89
+ "heading-3": heading("heading-3"),
90
+ "heading-4": heading("heading-4")
91
+ }
92
+ };
93
+ }
94
+ function emailThemeFromCss(cssSources, mode = "light", rootFontSize = 16) {
95
+ return createEmailTheme(tokenMaps(cssSources)[mode], rootFontSize);
96
+ }
97
+ //#endregion
98
+ export { createEmailTheme, emailThemeFromCss };
@@ -1,8 +1,6 @@
1
1
  //#region src/extensions/create-track-highlight.d.ts
2
2
  type TrackHighlightOptions = {
3
- /** Selector for highlightable rows, resolved within track. */
4
3
  itemSelector: string;
5
- /** Selector for resting row (active/selected) used when nothing is hovered. */
6
4
  activeSelector: string;
7
5
  range?: boolean;
8
6
  followHover?: boolean;