@tenphi/renderer 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +23 -0
  3. package/dist/client/appearance.d.ts +12 -0
  4. package/dist/client/appearance.d.ts.map +1 -0
  5. package/dist/client/appearance.js +66 -0
  6. package/dist/client/appearance.js.map +1 -0
  7. package/dist/client/code-copy.d.ts +1 -0
  8. package/dist/client/code-copy.js +63 -0
  9. package/dist/client/code-copy.js.map +1 -0
  10. package/dist/client/heading-links.d.ts +6 -0
  11. package/dist/client/heading-links.d.ts.map +1 -0
  12. package/dist/client/heading-links.js +87 -0
  13. package/dist/client/heading-links.js.map +1 -0
  14. package/dist/client/mobile-toc.d.ts +1 -0
  15. package/dist/client/mobile-toc.js +28 -0
  16. package/dist/client/mobile-toc.js.map +1 -0
  17. package/dist/client/navigation.d.ts +1 -0
  18. package/dist/client/navigation.js +112 -0
  19. package/dist/client/navigation.js.map +1 -0
  20. package/dist/client/page-copy.d.ts +1 -0
  21. package/dist/client/page-copy.js +34 -0
  22. package/dist/client/page-copy.js.map +1 -0
  23. package/dist/client/search.d.ts +13 -0
  24. package/dist/client/search.d.ts.map +1 -0
  25. package/dist/client/search.js +87 -0
  26. package/dist/client/search.js.map +1 -0
  27. package/dist/client/sidebar.d.ts +1 -0
  28. package/dist/client/sidebar.js +93 -0
  29. package/dist/client/sidebar.js.map +1 -0
  30. package/dist/client/tabs.d.ts +6 -0
  31. package/dist/client/tabs.d.ts.map +1 -0
  32. package/dist/client/tabs.js +64 -0
  33. package/dist/client/tabs.js.map +1 -0
  34. package/dist/components/Callout.astro +15 -0
  35. package/dist/components/Card.astro +13 -0
  36. package/dist/components/CodeGroup.astro +47 -0
  37. package/dist/components/ContrastIcon.astro +29 -0
  38. package/dist/components/EditLink.astro +11 -0
  39. package/dist/components/GlobalStyles.js +1675 -0
  40. package/dist/components/HeaderLinks.astro +84 -0
  41. package/dist/components/Icon.astro +39 -0
  42. package/dist/components/LastUpdated.astro +17 -0
  43. package/dist/components/LayoutComponents.js +866 -0
  44. package/dist/components/Logo.astro +37 -0
  45. package/dist/components/MobileNavigationTabs.astro +79 -0
  46. package/dist/components/MobileTocList.astro +22 -0
  47. package/dist/components/PackageVersion.astro +25 -0
  48. package/dist/components/PageActions.astro +29 -0
  49. package/dist/components/Pagination.astro +29 -0
  50. package/dist/components/Preview.astro +34 -0
  51. package/dist/components/SidebarList.astro +122 -0
  52. package/dist/components/SiteLogo.astro +40 -0
  53. package/dist/components/SiteTitle.astro +7 -0
  54. package/dist/components/Steps.astro +6 -0
  55. package/dist/components/Tab.astro +10 -0
  56. package/dist/components/TableOfContents.astro +23 -0
  57. package/dist/components/Tabs.astro +16 -0
  58. package/dist/components/TastyComponents.js +255 -0
  59. package/dist/components/VersionSwitcher.astro +44 -0
  60. package/dist/components/appearance-preferences.test.ts +91 -0
  61. package/dist/components/appearance-preferences.ts +57 -0
  62. package/dist/components/component-styles.test.ts +129 -0
  63. package/dist/components/component-styles.ts +65 -0
  64. package/dist/components/customize-component.js +8 -0
  65. package/dist/components/props.d.ts +38 -0
  66. package/dist/components/props.d.ts.map +1 -0
  67. package/dist/components/props.js +1 -0
  68. package/dist/components/props.ts +30 -0
  69. package/dist/components/svg-icon.test.ts +11 -0
  70. package/dist/components/svg-icon.ts +11 -0
  71. package/dist/components/tasty-states.d.ts +2 -0
  72. package/dist/components/tasty-states.js +42 -0
  73. package/dist/components-public.d.ts +21 -0
  74. package/dist/components.d.ts +10 -0
  75. package/dist/components.js +9 -0
  76. package/dist/content.d.ts +11 -0
  77. package/dist/content.d.ts.map +1 -0
  78. package/dist/content.js +16 -0
  79. package/dist/content.js.map +1 -0
  80. package/dist/eslint-plugin.d.ts +32 -0
  81. package/dist/eslint-plugin.d.ts.map +1 -0
  82. package/dist/eslint-plugin.js +155 -0
  83. package/dist/eslint-plugin.js.map +1 -0
  84. package/dist/icons/arrow-left.svg +1 -0
  85. package/dist/icons/arrow-right.svg +1 -0
  86. package/dist/icons/check.svg +1 -0
  87. package/dist/icons/chevron-down.svg +1 -0
  88. package/dist/icons/chevron-right.svg +1 -0
  89. package/dist/icons/close.svg +1 -0
  90. package/dist/icons/copy.svg +1 -0
  91. package/dist/icons/device-desktop.svg +1 -0
  92. package/dist/icons/favicon.svg +11 -0
  93. package/dist/icons/menu.svg +1 -0
  94. package/dist/icons/moon.svg +1 -0
  95. package/dist/icons/palette.svg +1 -0
  96. package/dist/icons/search.svg +1 -0
  97. package/dist/icons/sun.svg +1 -0
  98. package/dist/index-BuX_Sb5c.d.ts +10 -0
  99. package/dist/index-BuX_Sb5c.d.ts.map +1 -0
  100. package/dist/index.d.ts +23 -0
  101. package/dist/index.d.ts.map +1 -0
  102. package/dist/index.js +1648 -0
  103. package/dist/index.js.map +1 -0
  104. package/dist/localization.d.ts +69 -0
  105. package/dist/localization.d.ts.map +1 -0
  106. package/dist/localization.js +195 -0
  107. package/dist/localization.js.map +1 -0
  108. package/dist/markdown/rendered-content.d.ts +6 -0
  109. package/dist/markdown/rendered-content.d.ts.map +1 -0
  110. package/dist/markdown/rendered-content.js +34 -0
  111. package/dist/markdown/rendered-content.js.map +1 -0
  112. package/dist/messages-B_3xUzUb.js +14 -0
  113. package/dist/messages-B_3xUzUb.js.map +1 -0
  114. package/dist/navigation.d.ts +31 -0
  115. package/dist/navigation.d.ts.map +1 -0
  116. package/dist/navigation.js +165 -0
  117. package/dist/navigation.js.map +1 -0
  118. package/dist/overrides/DraftContentNotice.astro +8 -0
  119. package/dist/overrides/EmptyFooter.astro +3 -0
  120. package/dist/overrides/Footer.astro +23 -0
  121. package/dist/overrides/Head.astro +59 -0
  122. package/dist/overrides/Header.astro +139 -0
  123. package/dist/overrides/Hero.astro +73 -0
  124. package/dist/overrides/LanguageSelect.astro +63 -0
  125. package/dist/overrides/MarkdownContent.astro +14 -0
  126. package/dist/overrides/MobileMenuFooter.astro +15 -0
  127. package/dist/overrides/MobileMenuToggle.astro +36 -0
  128. package/dist/overrides/MobileTableOfContents.astro +37 -0
  129. package/dist/overrides/PageFrame.astro +35 -0
  130. package/dist/overrides/Search.astro +64 -0
  131. package/dist/overrides/Sidebar.astro +89 -0
  132. package/dist/overrides/SocialIcons.astro +27 -0
  133. package/dist/overrides/ThemeSelect.astro +148 -0
  134. package/dist/page-metadata.d.ts +13 -0
  135. package/dist/page-metadata.d.ts.map +1 -0
  136. package/dist/page-metadata.js +109 -0
  137. package/dist/page-metadata.js.map +1 -0
  138. package/dist/route-context.d.ts +85 -0
  139. package/dist/route-context.d.ts.map +1 -0
  140. package/dist/route-context.js +116 -0
  141. package/dist/route-context.js.map +1 -0
  142. package/dist/routes/DocsPage.astro +91 -0
  143. package/dist/routes/NotFound.astro +12 -0
  144. package/dist/routes/Page.astro +120 -0
  145. package/dist/styling.d.ts +17 -0
  146. package/dist/styling.d.ts.map +1 -0
  147. package/dist/styling.js +17 -0
  148. package/dist/styling.js.map +1 -0
  149. package/dist/tasty-states-BDKIbfw2.js +65 -0
  150. package/dist/tasty-states-BDKIbfw2.js.map +1 -0
  151. package/dist/theme/defaults.d.ts +21 -0
  152. package/dist/theme/defaults.d.ts.map +1 -0
  153. package/dist/theme/defaults.js +93 -0
  154. package/dist/theme/defaults.js.map +1 -0
  155. package/dist/theme/fonts.ts +64 -0
  156. package/dist/theme/shiki-theme.d.ts +13 -0
  157. package/dist/theme/shiki-theme.d.ts.map +1 -0
  158. package/dist/theme/shiki-theme.js +328 -0
  159. package/dist/theme/shiki-theme.js.map +1 -0
  160. package/dist/theme/tasty-config.d.ts +13 -0
  161. package/dist/theme/tasty-config.d.ts.map +1 -0
  162. package/dist/theme/tasty-config.js +16 -0
  163. package/dist/theme/tasty-config.js.map +1 -0
  164. package/dist/ui.d.ts +6 -0
  165. package/dist/ui.d.ts.map +1 -0
  166. package/dist/ui.js +53 -0
  167. package/dist/ui.js.map +1 -0
  168. package/dist/versioning.d.ts +11 -0
  169. package/dist/versioning.d.ts.map +1 -0
  170. package/dist/versioning.js +38 -0
  171. package/dist/versioning.js.map +1 -0
  172. package/package.json +92 -0
  173. package/tasty.config.mjs +1 -0
@@ -0,0 +1,255 @@
1
+ import { tasty } from "@tenphi/tasty";
2
+ import { customizeComponent } from "./customize-component.js";
3
+ import { configureCookbookStates } from "./tasty-states.js";
4
+
5
+ configureCookbookStates();
6
+
7
+ export const CardRoot = customizeComponent(
8
+ "Card",
9
+ tasty({
10
+ as: "article",
11
+ styles: {
12
+ display: "grid",
13
+ gap: "1.5x",
14
+ padding: "2.5x",
15
+ color: "#text",
16
+ textDecoration: "none",
17
+ border: true,
18
+ radius: "1cr",
19
+ fill: "#surface-2",
20
+ shadow: "0 1px 2px #shadow",
21
+ transition: "fill $transition, shadow $transition, translate $transition",
22
+
23
+ Heading2: {
24
+ $: "h2",
25
+ margin: "0",
26
+ },
27
+ Heading3: {
28
+ $: "h3",
29
+ margin: "0",
30
+ },
31
+ Paragraph: {
32
+ $: "p",
33
+ margin: "0",
34
+ },
35
+ },
36
+ }),
37
+ );
38
+
39
+ export const CardLink = tasty(CardRoot, {
40
+ as: "a",
41
+ styles: {
42
+ fill: {
43
+ "": "#surface-2",
44
+ ":hover": "#surface-2-hover",
45
+ ":active": "#surface-2-pressed",
46
+ },
47
+ shadow: {
48
+ "": "0 1px 2px #shadow",
49
+ ":hover": "0 .75x 2x #shadow",
50
+ },
51
+ translate: {
52
+ "": "0",
53
+ ":hover": "0 -1px",
54
+ ":active": "0",
55
+ },
56
+ },
57
+ });
58
+
59
+ const previewElements = {
60
+ Caption: "figcaption",
61
+ Stage: "div",
62
+ Frame: "iframe",
63
+ Code: "details",
64
+ Summary: "summary",
65
+ Pre: "pre",
66
+ };
67
+
68
+ export const PreviewRoot = customizeComponent(
69
+ "Preview",
70
+ tasty({
71
+ as: "figure",
72
+ styles: {
73
+ margin: "0",
74
+ overflow: "clip",
75
+ color: "#text",
76
+ border: true,
77
+ radius: "1cr",
78
+ fill: "#surface",
79
+ shadow: "0 1px 2px #shadow",
80
+
81
+ Caption: {
82
+ padding: "1.5x 2x",
83
+ preset: "small",
84
+ fill: "#surface-2",
85
+ },
86
+ Stage: {
87
+ padding: "3x",
88
+ fill: "#surface",
89
+ },
90
+ Frame: {
91
+ display: "block",
92
+ width: "100%",
93
+ height: "min 18rem",
94
+ border: "0",
95
+ fill: "#surface",
96
+ },
97
+ Code: {
98
+ border: "1bw top #border",
99
+ },
100
+ Summary: {
101
+ padding: "1.5x 2x",
102
+ preset: "small",
103
+ fill: "#surface-2",
104
+ cursor: "pointer",
105
+ },
106
+ Pre: {
107
+ margin: "0",
108
+ radius: "0",
109
+ },
110
+ },
111
+ elements: previewElements,
112
+ }),
113
+ );
114
+
115
+ export const StepsRoot = customizeComponent(
116
+ "Steps",
117
+ tasty({
118
+ as: "ol",
119
+ styles: {
120
+ display: "grid",
121
+ gap: "2x",
122
+ padding: "4x left",
123
+ border: "1bw left #border",
124
+
125
+ Item: {
126
+ $: "> li",
127
+ padding: "1x left",
128
+ },
129
+ Marker: {
130
+ $: "> li::marker",
131
+ color: "#accent-text",
132
+ preset: "strong",
133
+ },
134
+ },
135
+ }),
136
+ );
137
+
138
+ export const TabsRoot = customizeComponent(
139
+ "Tabs",
140
+ tasty({
141
+ as: "div",
142
+ styles: {
143
+ margin: "2x 0",
144
+ border: true,
145
+ radius: "1cr",
146
+ overflow: "clip",
147
+ fill: "#surface",
148
+ List: {
149
+ padding: "1x",
150
+ display: "flex",
151
+ gap: "1x",
152
+ overflow: "auto",
153
+ fill: "#surface-2",
154
+ },
155
+ Button: {
156
+ $: "> [data-tabs-list] > button",
157
+ margin: "0",
158
+ preset: "navigation",
159
+ padding: "1x 2x",
160
+ border: "0",
161
+ radius: "1r",
162
+ color: "#text",
163
+ fill: "#clear",
164
+ cursor: "pointer",
165
+ whiteSpace: "nowrap",
166
+ },
167
+ SelectedButton: {
168
+ $: '> [data-tabs-list] > button[aria-selected="true"]',
169
+ color: "#accent-surface-text",
170
+ fill: "#accent-surface",
171
+ },
172
+ FocusedButton: {
173
+ $: "> [data-tabs-list] > button:focus-visible",
174
+ outline: "2px #focus / -2px",
175
+ },
176
+ },
177
+ elements: { List: "div" },
178
+ }),
179
+ );
180
+
181
+ export const TabRoot = customizeComponent(
182
+ "Tab",
183
+ tasty({
184
+ as: "section",
185
+ styles: {
186
+ margin: "0",
187
+ padding: "2x",
188
+ Heading: { margin: "0 0 1x", preset: "h3" },
189
+ Hidden: { $: "&[hidden]", hide: true },
190
+ HiddenHeading: { $: "> [data-tab-heading][hidden]", hide: true },
191
+ },
192
+ elements: { Heading: "h3" },
193
+ }),
194
+ );
195
+
196
+ export const CalloutRoot = customizeComponent(
197
+ "Callout",
198
+ tasty({
199
+ as: "aside",
200
+ styles: {
201
+ "#callout-border": "#info",
202
+ "#callout-text": "#info-text",
203
+ "#callout-surface": "#info-surface",
204
+ margin: "2x 0",
205
+ padding: "2x 3x",
206
+ border: "1bw #callout-border",
207
+ radius: "1cr",
208
+ fill: "#callout-surface",
209
+ color: "#text",
210
+ Title: {
211
+ preset: "body / strong",
212
+ color: "#callout-text",
213
+ margin: "0 0 1x",
214
+ },
215
+ Body: { color: "#text", margin: "0" },
216
+ Tip: {
217
+ $: '&[data-kind="tip"]',
218
+ "#callout-border": "#success",
219
+ "#callout-text": "#success-text",
220
+ "#callout-surface": "#success-surface",
221
+ },
222
+ Caution: {
223
+ $: '&[data-kind="caution"]',
224
+ "#callout-border": "#warning",
225
+ "#callout-text": "#warning-text",
226
+ "#callout-surface": "#warning-surface",
227
+ },
228
+ Danger: {
229
+ $: '&[data-kind="danger"]',
230
+ "#callout-border": "#danger",
231
+ "#callout-text": "#danger-text",
232
+ "#callout-surface": "#danger-surface",
233
+ },
234
+ },
235
+ elements: { Title: "p", Body: "div" },
236
+ }),
237
+ );
238
+
239
+ export const CodeGroupRoot = customizeComponent(
240
+ "CodeGroup",
241
+ tasty({
242
+ as: "div",
243
+ styles: {
244
+ Caption: { preset: "small", color: "#text-soft", margin: "0 0 1x" },
245
+ Pre: { $: "pre" },
246
+ Code: {
247
+ $: "pre code",
248
+ preset: "code",
249
+ color: "#syntax-text",
250
+ whiteSpace: "pre",
251
+ },
252
+ },
253
+ elements: { Caption: "p", Pre: "pre", Code: "code" },
254
+ }),
255
+ );
@@ -0,0 +1,44 @@
1
+ ---
2
+ import { messages } from "../localization.js";
3
+ import { content } from "virtual:cookbook/config";
4
+ import type { DocsRoute, SiteVersion } from "@tenphi/docs";
5
+ import { versionLinks } from "../versioning.js";
6
+ import { VersionSwitcherRoot } from "./LayoutComponents.js";
7
+
8
+ interface Props {
9
+ versions: SiteVersion[];
10
+ routes: DocsRoute[];
11
+ currentPath: string;
12
+ base: string;
13
+ }
14
+
15
+ const { current, links } = versionLinks(
16
+ Astro.props.versions,
17
+ Astro.props.routes,
18
+ Astro.props.currentPath,
19
+ Astro.props.base,
20
+ content,
21
+ );
22
+ const ui = messages(Astro.locals.cookbookRoute?.lang ?? "en", content);
23
+ ---
24
+
25
+ <VersionSwitcherRoot data-tasty-anatomy="VersionSwitcher">
26
+ <summary
27
+ aria-label={`${ui.documentationVersion}: ${current?.label ?? ui.versions}`}
28
+ >{current?.label ?? ui.versions}</summary
29
+ >
30
+ <nav aria-label={ui.documentationVersions}>
31
+ {
32
+ links.map((version) => (
33
+ <a
34
+ href={version.href}
35
+ aria-current={
36
+ version.routeBase === current?.routeBase ? "page" : undefined
37
+ }
38
+ >
39
+ {version.label}
40
+ </a>
41
+ ))
42
+ }
43
+ </nav>
44
+ </VersionSwitcherRoot>
@@ -0,0 +1,91 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { loadPreferences, storePreferences } from "./appearance-preferences.js";
3
+
4
+ function storage(values: Record<string, string> = {}) {
5
+ return {
6
+ getItem: (key: string) => values[key] ?? null,
7
+ setItem: (key: string, value: string) => {
8
+ values[key] = value;
9
+ },
10
+ };
11
+ }
12
+
13
+ describe("appearance preferences", () => {
14
+ it("restores existing independent scheme and contrast choices", () => {
15
+ const saved = storage({
16
+ "cookbook-theme": "dark",
17
+ "cookbook-appearance": JSON.stringify({ contrast: "more" }),
18
+ });
19
+ expect(loadPreferences(saved)).toEqual({
20
+ scheme: "dark",
21
+ contrast: "more",
22
+ });
23
+ storePreferences({ scheme: "light", contrast: "more" }, saved);
24
+ expect(loadPreferences(saved)).toEqual({
25
+ scheme: "light",
26
+ contrast: "more",
27
+ });
28
+ storePreferences({ scheme: "light", contrast: "normal" }, saved);
29
+ expect(loadPreferences(saved)).toEqual({
30
+ scheme: "light",
31
+ contrast: "normal",
32
+ });
33
+ });
34
+
35
+ it("persists Auto using Cookbook storage without losing other preferences", () => {
36
+ const saved = storage({
37
+ "cookbook-appearance": JSON.stringify({ contrast: "more", other: true }),
38
+ });
39
+ storePreferences({ scheme: "auto", contrast: "system" }, saved);
40
+ expect(saved.getItem("cookbook-theme")).toBe("");
41
+ expect(JSON.parse(saved.getItem("cookbook-appearance")!)).toEqual({
42
+ contrast: "system",
43
+ other: true,
44
+ });
45
+ expect(loadPreferences(saved)).toEqual({
46
+ scheme: "auto",
47
+ contrast: "system",
48
+ });
49
+ });
50
+
51
+ it.each(["{broken", "null", "[]", '"more"', '{"contrast":"invalid"}'])(
52
+ "recovers from malformed contrast data: %s",
53
+ (value) => {
54
+ const saved = storage({
55
+ "cookbook-theme": "light",
56
+ "cookbook-appearance": value,
57
+ });
58
+ expect(loadPreferences(saved)).toEqual({
59
+ scheme: "light",
60
+ contrast: "system",
61
+ });
62
+ storePreferences({ scheme: "dark", contrast: "more" }, saved);
63
+ expect(loadPreferences(saved)).toEqual({
64
+ scheme: "dark",
65
+ contrast: "more",
66
+ });
67
+ },
68
+ );
69
+
70
+ it("uses system defaults for unknown values or unavailable storage", () => {
71
+ expect(loadPreferences(storage({ "cookbook-theme": "invalid" }))).toEqual({
72
+ scheme: "auto",
73
+ contrast: "system",
74
+ });
75
+ const unavailable = {
76
+ getItem: () => {
77
+ throw new Error("Storage unavailable");
78
+ },
79
+ setItem: () => {
80
+ throw new Error("Storage unavailable");
81
+ },
82
+ };
83
+ expect(loadPreferences(unavailable)).toEqual({
84
+ scheme: "auto",
85
+ contrast: "system",
86
+ });
87
+ expect(() =>
88
+ storePreferences({ scheme: "dark", contrast: "more" }, unavailable),
89
+ ).not.toThrow();
90
+ });
91
+ });
@@ -0,0 +1,57 @@
1
+ type Scheme = "light" | "dark" | "auto";
2
+ type Contrast = "normal" | "more" | "system";
3
+ type Preferences = { scheme: Scheme; contrast: Contrast };
4
+ type PreferenceStorage = Pick<Storage, "getItem" | "setItem">;
5
+
6
+ export const parseScheme = (value: unknown): Scheme =>
7
+ value === "light" || value === "dark" ? value : "auto";
8
+
9
+ export const parseContrast = (value: unknown): Contrast =>
10
+ value === "normal" || value === "more" ? value : "system";
11
+
12
+ function storedAppearance(storage: PreferenceStorage): Record<string, unknown> {
13
+ try {
14
+ const value: unknown = JSON.parse(
15
+ storage.getItem("cookbook-appearance") ?? "{}",
16
+ );
17
+ return value && typeof value === "object" && !Array.isArray(value)
18
+ ? (value as Record<string, unknown>)
19
+ : {};
20
+ } catch {
21
+ return {};
22
+ }
23
+ }
24
+
25
+ export function loadPreferences(storage?: PreferenceStorage): Preferences {
26
+ const preferences: Preferences = { scheme: "auto", contrast: "system" };
27
+ try {
28
+ storage ??= localStorage;
29
+ preferences.scheme = parseScheme(storage.getItem("cookbook-theme"));
30
+ preferences.contrast = parseContrast(storedAppearance(storage).contrast);
31
+ } catch {
32
+ // System defaults work even when browser storage is unavailable.
33
+ }
34
+ return preferences;
35
+ }
36
+
37
+ export function storePreferences(
38
+ value: Preferences,
39
+ storage?: PreferenceStorage,
40
+ ): void {
41
+ try {
42
+ storage ??= localStorage;
43
+ storage.setItem(
44
+ "cookbook-theme",
45
+ value.scheme === "auto" ? "" : value.scheme,
46
+ );
47
+ storage.setItem(
48
+ "cookbook-appearance",
49
+ JSON.stringify({
50
+ ...storedAppearance(storage),
51
+ contrast: value.contrast,
52
+ }),
53
+ );
54
+ } catch {
55
+ // The current page still updates when preferences cannot be persisted.
56
+ }
57
+ }
@@ -0,0 +1,129 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import {
3
+ configureComponentStyles,
4
+ resolveLegacyAnatomyStyles,
5
+ resolveComponentStyleOverride,
6
+ resolveComponentStyles,
7
+ unusedCustomStyleNames,
8
+ } from "./component-styles.js";
9
+
10
+ const defaults = {
11
+ color: "#text",
12
+ padding: "2x",
13
+ Label: { color: "#text-soft", fontWeight: 500 },
14
+ };
15
+
16
+ afterEach(() => configureComponentStyles(undefined));
17
+
18
+ describe("component style configuration", () => {
19
+ it("extends defaults with a plain Tasty style object", () => {
20
+ configureComponentStyles({
21
+ Card: { padding: "3x", Label: { color: "#accent-text" } },
22
+ });
23
+
24
+ expect(resolveComponentStyles("Card", defaults)).toEqual({
25
+ color: "#text",
26
+ padding: "3x",
27
+ Label: { color: "#accent-text", fontWeight: 500 },
28
+ });
29
+ });
30
+
31
+ it("returns the user override for Tasty component composition", () => {
32
+ expect(resolveComponentStyleOverride("Card")).toBeUndefined();
33
+
34
+ configureComponentStyles({ Card: { padding: "3x" } });
35
+ expect(resolveComponentStyleOverride("Card")).toEqual({
36
+ padding: "3x",
37
+ });
38
+ });
39
+
40
+ it("merges configuration into bridged component style trees", () => {
41
+ configureComponentStyles({
42
+ MarkdownTable: { Table: { radius: "0" } },
43
+ Mermaid: { Diagram: { maxInlineSize: "90%" } },
44
+ MobileTableOfContents: { LinkLabel: { whiteSpace: "normal" } },
45
+ Sidebar: { Link: { padding: "1x" } },
46
+ TableOfContents: { Link: { color: "#text" } },
47
+ });
48
+
49
+ expect(
50
+ resolveComponentStyles("MarkdownTable", {
51
+ Table: { border: true, radius: "1cr" },
52
+ Cell: { padding: "1.5x 2x" },
53
+ }),
54
+ ).toEqual({
55
+ Table: { border: true, radius: "0" },
56
+ Cell: { padding: "1.5x 2x" },
57
+ });
58
+ expect(
59
+ resolveComponentStyles("Mermaid", {
60
+ Diagram: { display: "block", maxInlineSize: "100%" },
61
+ }),
62
+ ).toEqual({
63
+ Diagram: { display: "block", maxInlineSize: "90%" },
64
+ });
65
+ expect(
66
+ resolveComponentStyles("Sidebar", {
67
+ display: "block",
68
+ Content: { gap: "3x" },
69
+ Link: { color: "#text-soft", padding: "2x" },
70
+ }),
71
+ ).toEqual({
72
+ display: "block",
73
+ Content: { gap: "3x" },
74
+ Link: { color: "#text-soft", padding: "1x" },
75
+ });
76
+ expect(
77
+ resolveComponentStyles("TableOfContents", {
78
+ paddingBlockStart: "5x",
79
+ Heading: { color: "#text" },
80
+ Link: { color: "#text-muted", preset: "small" },
81
+ }),
82
+ ).toEqual({
83
+ paddingBlockStart: "5x",
84
+ Heading: { color: "#text" },
85
+ Link: { color: "#text", preset: "small" },
86
+ });
87
+ expect(
88
+ resolveComponentStyles("MobileTableOfContents", {
89
+ display: "grid",
90
+ Item: { minInlineSize: "0" },
91
+ LinkLabel: { overflow: "hidden", whiteSpace: "nowrap" },
92
+ }),
93
+ ).toEqual({
94
+ display: "grid",
95
+ Item: { minInlineSize: "0" },
96
+ LinkLabel: { overflow: "hidden", whiteSpace: "normal" },
97
+ });
98
+ });
99
+
100
+ it("keeps custom anatomy styles on the compatibility bridge", () => {
101
+ expect(
102
+ resolveLegacyAnatomyStyles({
103
+ Footer: { color: "#text-muted" },
104
+ ThemeSelect: { padding: "1x" },
105
+ ProductBadge: { color: "#accent-text" },
106
+ }),
107
+ ).toEqual({
108
+ '[data-tasty-anatomy="ProductBadge"]': { color: "#accent-text" },
109
+ });
110
+ });
111
+
112
+ it("reports only custom style names without a component or anatomy match", () => {
113
+ const styles = {
114
+ ProjectBadge: { color: "#accent-text" },
115
+ ProjectNote: { color: "#text" },
116
+ ProjectBadg: { color: "#text-muted" },
117
+ };
118
+ configureComponentStyles(styles);
119
+ resolveComponentStyles("ProjectBadge", { color: "#text" });
120
+
121
+ expect(unusedCustomStyleNames(styles, new Set(["ProjectNote"]))).toEqual([
122
+ "ProjectBadg",
123
+ ]);
124
+ configureComponentStyles(styles);
125
+ expect(unusedCustomStyleNames(styles, new Set())).toEqual(
126
+ Object.keys(styles),
127
+ );
128
+ });
129
+ });
@@ -0,0 +1,65 @@
1
+ import { mergeStyles, type Styles } from "@tenphi/tasty/core";
2
+ import { COOKBOOK_COMPONENT_NAMES } from "@tenphi/docs";
3
+
4
+ // Astro can load the integration and renderer through separate module graphs.
5
+ // Keep their component configuration on the shared process global.
6
+ const sharedConfiguration = globalThis as typeof globalThis & {
7
+ __tenphiCookbookComponentStyles?: Record<string, Styles | undefined>;
8
+ __tenphiCookbookUsedComponentStyles?: Set<string>;
9
+ };
10
+ const cookbookComponentNames = new Set<string>(COOKBOOK_COMPONENT_NAMES);
11
+
12
+ export function configureComponentStyles(
13
+ styles: Record<string, Styles | undefined> | undefined,
14
+ ): void {
15
+ sharedConfiguration.__tenphiCookbookComponentStyles = styles ?? {};
16
+ sharedConfiguration.__tenphiCookbookUsedComponentStyles = new Set();
17
+ }
18
+
19
+ /** Merge a partial theme override into a built-in or consumer style tree. */
20
+ export function resolveComponentStyles(
21
+ name: string,
22
+ baseStyles: Styles,
23
+ ): Styles {
24
+ sharedConfiguration.__tenphiCookbookUsedComponentStyles?.add(name);
25
+ const configuredStyles = sharedConfiguration
26
+ .__tenphiCookbookComponentStyles?.[name] as Styles | undefined;
27
+ return configuredStyles
28
+ ? mergeStyles(baseStyles, configuredStyles as Styles)
29
+ : baseStyles;
30
+ }
31
+
32
+ /** Find custom names without a component style resolver or rendered anatomy. */
33
+ export function unusedCustomStyleNames(
34
+ styles: Record<string, Styles> | undefined,
35
+ anatomyNames: ReadonlySet<string>,
36
+ ): string[] {
37
+ return Object.keys(styles ?? {}).filter(
38
+ (name) =>
39
+ !sharedConfiguration.__tenphiCookbookUsedComponentStyles?.has(name) &&
40
+ !anatomyNames.has(name),
41
+ );
42
+ }
43
+
44
+ export function resolveComponentStyleOverride(
45
+ name: string,
46
+ ): Styles | undefined {
47
+ return sharedConfiguration.__tenphiCookbookComponentStyles?.[name] as
48
+ Styles | undefined;
49
+ }
50
+
51
+ /** Preserve custom anatomy names from the pre-component style API. */
52
+ export function resolveLegacyAnatomyStyles(
53
+ styles: Record<string, Styles | undefined> | undefined,
54
+ ): Record<string, Styles> | undefined {
55
+ if (!styles) return undefined;
56
+ const entries = Object.entries(styles)
57
+ .filter(
58
+ (entry): entry is [string, Styles] =>
59
+ !cookbookComponentNames.has(entry[0]) && entry[1] !== undefined,
60
+ )
61
+ .map(([name, value]) => [`[data-tasty-anatomy="${name}"]`, value]);
62
+ return entries.length
63
+ ? (Object.fromEntries(entries) as Record<string, Styles>)
64
+ : undefined;
65
+ }
@@ -0,0 +1,8 @@
1
+ import { tasty } from "@tenphi/tasty";
2
+ import { resolveComponentStyleOverride } from "./component-styles.js";
3
+
4
+ export function customizeComponent(name, base) {
5
+ const override = resolveComponentStyleOverride(name);
6
+ if (!override) return base;
7
+ return tasty(base, { styles: override });
8
+ }
@@ -0,0 +1,38 @@
1
+ //#region src/components/props.d.ts
2
+ interface CardProps {
3
+ title?: string;
4
+ href?: string;
5
+ }
6
+ interface LogoProps {
7
+ label?: string;
8
+ decorative?: boolean;
9
+ class?: string;
10
+ }
11
+ interface PreviewProps {
12
+ title: string;
13
+ html?: string;
14
+ css?: string;
15
+ javascript?: string;
16
+ }
17
+ type StepsProps = Record<string, never>;
18
+ interface TabsProps {
19
+ label?: string;
20
+ }
21
+ interface TabProps {
22
+ label: string;
23
+ }
24
+ interface CalloutProps {
25
+ title?: string;
26
+ kind?: "note" | "tip" | "caution" | "danger";
27
+ }
28
+ interface CodeGroupProps {
29
+ label?: string;
30
+ items: Array<{
31
+ label: string;
32
+ code: string;
33
+ language?: string;
34
+ }>;
35
+ }
36
+ //#endregion
37
+ export { CalloutProps, CardProps, CodeGroupProps, LogoProps, PreviewProps, StepsProps, TabProps, TabsProps };
38
+ //# sourceMappingURL=props.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"props.d.ts","names":[],"sources":["../../src/components/props.ts"],"mappings":";UAAiB;EACf;EACA;;UAEe;EACf;EACA;EACA;;UAEe;EACf;EACA;EACA;EACA;;KAEU,aAAa;UACR;EACf;;UAEe;EACf;;UAEe;EACf;EACA;;UAEe;EACf;EACA,OAAO;IAAQ;IAAe;IAAc"}
@@ -0,0 +1 @@
1
+ export {};