singularity-components 0.1.195 β†’ 0.1.197

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 (166) hide show
  1. package/dist/components/blocks/badges/category-badge.d.ts +11 -0
  2. package/dist/components/blocks/badges/category-badge.js +34 -0
  3. package/dist/components/blocks/badges/category-badge.js.map +1 -0
  4. package/dist/components/blocks/cards/blogpost-card.d.ts +3 -1
  5. package/dist/components/blocks/cards/blogpost-card.js +10 -5
  6. package/dist/components/blocks/cards/blogpost-card.js.map +1 -1
  7. package/dist/components/blocks/directory/author-card.d.ts +10 -0
  8. package/dist/components/blocks/directory/author-card.js +50 -0
  9. package/dist/components/blocks/directory/author-card.js.map +1 -0
  10. package/dist/components/blocks/directory/category-card.d.ts +10 -0
  11. package/dist/components/blocks/directory/category-card.js +27 -0
  12. package/dist/components/blocks/directory/category-card.js.map +1 -0
  13. package/dist/components/blocks/extras/extras-hub-card.d.ts +16 -0
  14. package/dist/components/blocks/extras/extras-hub-card.js +22 -0
  15. package/dist/components/blocks/extras/extras-hub-card.js.map +1 -0
  16. package/dist/components/blocks/gallery/image-gallery.d.ts +14 -0
  17. package/dist/components/blocks/gallery/image-gallery.js +211 -0
  18. package/dist/components/blocks/gallery/image-gallery.js.map +1 -0
  19. package/dist/components/blocks/index.d.ts +11 -0
  20. package/dist/components/blocks/index.js +10 -0
  21. package/dist/components/blocks/index.js.map +1 -1
  22. package/dist/components/blocks/loading/loading-skeletons.d.ts +15 -0
  23. package/dist/components/blocks/loading/loading-skeletons.js +78 -0
  24. package/dist/components/blocks/loading/loading-skeletons.js.map +1 -0
  25. package/dist/components/blocks/login/login.js +76 -47
  26. package/dist/components/blocks/login/login.js.map +1 -1
  27. package/dist/components/blocks/marketing/page-hero.d.ts +13 -0
  28. package/dist/components/blocks/marketing/page-hero.js +37 -0
  29. package/dist/components/blocks/marketing/page-hero.js.map +1 -0
  30. package/dist/components/blocks/marketing/stats-grid.d.ts +16 -0
  31. package/dist/components/blocks/marketing/stats-grid.js +30 -0
  32. package/dist/components/blocks/marketing/stats-grid.js.map +1 -0
  33. package/dist/components/blocks/marketing/timeline.d.ts +17 -0
  34. package/dist/components/blocks/marketing/timeline.js +46 -0
  35. package/dist/components/blocks/marketing/timeline.js.map +1 -0
  36. package/dist/components/blocks/marketing/values-grid.d.ts +16 -0
  37. package/dist/components/blocks/marketing/values-grid.js +29 -0
  38. package/dist/components/blocks/marketing/values-grid.js.map +1 -0
  39. package/dist/components/blocks/post-list/post-list-with-filters.js +4 -4
  40. package/dist/components/blocks/post-list/post-list-with-filters.js.map +1 -1
  41. package/dist/components/index.d.ts +28 -1
  42. package/dist/components/pages/about/about-page.d.ts +5 -0
  43. package/dist/components/pages/about/about-page.js +161 -0
  44. package/dist/components/pages/about/about-page.js.map +1 -0
  45. package/dist/components/pages/admin/admin-page.js +160 -103
  46. package/dist/components/pages/admin/admin-page.js.map +1 -1
  47. package/dist/components/pages/author/author-page.d.ts +8 -0
  48. package/dist/components/pages/author/author-page.js +107 -0
  49. package/dist/components/pages/author/author-page.js.map +1 -0
  50. package/dist/components/pages/authors/authors-page.d.ts +5 -0
  51. package/dist/components/pages/authors/authors-page.js +25 -0
  52. package/dist/components/pages/authors/authors-page.js.map +1 -0
  53. package/dist/components/pages/blogpost/blogpost.d.ts +4 -1
  54. package/dist/components/pages/blogpost/blogpost.js +110 -62
  55. package/dist/components/pages/blogpost/blogpost.js.map +1 -1
  56. package/dist/components/pages/categories/categories-page.d.ts +5 -0
  57. package/dist/components/pages/categories/categories-page.js +33 -0
  58. package/dist/components/pages/categories/categories-page.js.map +1 -0
  59. package/dist/components/pages/category/category-page.js +4 -2
  60. package/dist/components/pages/category/category-page.js.map +1 -1
  61. package/dist/components/pages/chat/chat-page.js +4 -4
  62. package/dist/components/pages/chat/chat-page.js.map +1 -1
  63. package/dist/components/pages/contact/contact-page.d.ts +5 -0
  64. package/dist/components/pages/contact/contact-page.js +180 -0
  65. package/dist/components/pages/contact/contact-page.js.map +1 -0
  66. package/dist/components/pages/content-blocks/content-blocks-page.d.ts +5 -0
  67. package/dist/components/pages/content-blocks/content-blocks-page.js +87 -0
  68. package/dist/components/pages/content-blocks/content-blocks-page.js.map +1 -0
  69. package/dist/components/pages/extras/extras-hub-page.d.ts +10 -0
  70. package/dist/components/pages/extras/extras-hub-page.js +110 -0
  71. package/dist/components/pages/extras/extras-hub-page.js.map +1 -0
  72. package/dist/components/pages/index.d.ts +14 -0
  73. package/dist/components/pages/index.js +12 -0
  74. package/dist/components/pages/index.js.map +1 -1
  75. package/dist/components/pages/maintenance/maintenance-page.js +1 -1
  76. package/dist/components/pages/maintenance/maintenance-page.js.map +1 -1
  77. package/dist/components/pages/membership/membership-page.d.ts +5 -0
  78. package/dist/components/pages/membership/membership-page.js +131 -0
  79. package/dist/components/pages/membership/membership-page.js.map +1 -0
  80. package/dist/components/pages/mosaic/mosaic-page.d.ts +5 -0
  81. package/dist/components/pages/mosaic/mosaic-page.js +81 -0
  82. package/dist/components/pages/mosaic/mosaic-page.js.map +1 -0
  83. package/dist/components/pages/newsletter/newsletter-page.d.ts +5 -0
  84. package/dist/components/pages/newsletter/newsletter-page.js +165 -0
  85. package/dist/components/pages/newsletter/newsletter-page.js.map +1 -0
  86. package/dist/components/pages/not-found/not-found.js +2 -2
  87. package/dist/components/pages/not-found/not-found.js.map +1 -1
  88. package/dist/components/pages/privacy/privacy-page.js +2 -2
  89. package/dist/components/pages/privacy/privacy-page.js.map +1 -1
  90. package/dist/components/pages/resources/resources-page.d.ts +5 -0
  91. package/dist/components/pages/resources/resources-page.js +24 -0
  92. package/dist/components/pages/resources/resources-page.js.map +1 -0
  93. package/dist/components/pages/startpage/startpage.js +6 -4
  94. package/dist/components/pages/startpage/startpage.js.map +1 -1
  95. package/dist/components/pages/terms/terms-page.js +2 -2
  96. package/dist/components/pages/terms/terms-page.js.map +1 -1
  97. package/dist/components/primitives/accordion/accordion.js +14 -16
  98. package/dist/components/primitives/accordion/accordion.js.map +1 -1
  99. package/dist/components/primitives/badge/badge.js +1 -1
  100. package/dist/components/primitives/badge/badge.js.map +1 -1
  101. package/dist/components/primitives/buttons/button.d.ts +2 -2
  102. package/dist/components/primitives/buttons/icon-button.d.ts +1 -1
  103. package/dist/components/primitives/collapsible/collapsible.js +4 -1
  104. package/dist/components/primitives/collapsible/collapsible.js.map +1 -1
  105. package/dist/components/primitives/dropdown-menu/dropdown-menu.js +6 -1
  106. package/dist/components/primitives/dropdown-menu/dropdown-menu.js.map +1 -1
  107. package/dist/components/primitives/forms/checkbox.js +1 -1
  108. package/dist/components/primitives/forms/checkbox.js.map +1 -1
  109. package/dist/components/primitives/forms/field.d.ts +4 -2
  110. package/dist/components/primitives/forms/field.js +4 -2
  111. package/dist/components/primitives/forms/field.js.map +1 -1
  112. package/dist/components/primitives/forms/form-control.d.ts +28 -0
  113. package/dist/components/primitives/forms/form-control.js +40 -0
  114. package/dist/components/primitives/forms/form-control.js.map +1 -0
  115. package/dist/components/primitives/forms/form.d.ts +12 -0
  116. package/dist/components/primitives/forms/form.js +30 -0
  117. package/dist/components/primitives/forms/form.js.map +1 -0
  118. package/dist/components/primitives/forms/select.js +12 -12
  119. package/dist/components/primitives/forms/select.js.map +1 -1
  120. package/dist/components/primitives/icon/icon.d.ts +3 -2
  121. package/dist/components/primitives/icon/icon.js +2 -1
  122. package/dist/components/primitives/icon/icon.js.map +1 -1
  123. package/dist/components/primitives/index.d.ts +4 -0
  124. package/dist/components/primitives/index.js +3 -0
  125. package/dist/components/primitives/index.js.map +1 -1
  126. package/dist/components/primitives/layout/layout.d.ts +1 -1
  127. package/dist/components/primitives/link/link.d.ts +2 -2
  128. package/dist/components/primitives/sheet/sheet.js +1 -1
  129. package/dist/components/primitives/sheet/sheet.js.map +1 -1
  130. package/dist/components/primitives/stack/stack.d.ts +2 -2
  131. package/dist/components/primitives/text/internal/text-element.d.ts +8 -2
  132. package/dist/components/primitives/text/internal/text-element.js +3 -0
  133. package/dist/components/primitives/text/internal/text-element.js.map +1 -1
  134. package/dist/components/primitives/text/text-code.d.ts +1 -1
  135. package/dist/components/templates/form/form.d.ts +2 -2
  136. package/dist/components/templates/form/form.js +133 -87
  137. package/dist/components/templates/form/form.js.map +1 -1
  138. package/dist/components/templates/hero/hero.js +1 -0
  139. package/dist/components/templates/hero/hero.js.map +1 -1
  140. package/dist/components/templates/index.d.ts +1 -0
  141. package/dist/components/templates/index.js +1 -0
  142. package/dist/components/templates/index.js.map +1 -1
  143. package/dist/components/templates/loading-screen/loading-screen.d.ts +10 -0
  144. package/dist/components/templates/loading-screen/loading-screen.js +39 -0
  145. package/dist/components/templates/loading-screen/loading-screen.js.map +1 -0
  146. package/dist/css/variables.css +6 -3
  147. package/dist/css/variables.css.map +1 -1
  148. package/dist/data/posts.d.ts +5 -0
  149. package/dist/data/posts.js +41 -8
  150. package/dist/data/posts.js.map +1 -1
  151. package/dist/index.d.ts +28 -1
  152. package/dist/lib/forms/field-props.d.ts +60 -0
  153. package/dist/lib/forms/field-props.js +60 -0
  154. package/dist/lib/forms/field-props.js.map +1 -0
  155. package/dist/lib/forms/index.d.ts +11 -0
  156. package/dist/lib/forms/index.js +3 -0
  157. package/dist/lib/forms/index.js.map +1 -0
  158. package/dist/lib/forms/tanstack-field.d.ts +71 -0
  159. package/dist/lib/forms/tanstack-field.js +121 -0
  160. package/dist/lib/forms/tanstack-field.js.map +1 -0
  161. package/dist/lib/index.d.ts +11 -0
  162. package/dist/lib/index.js +1 -0
  163. package/dist/lib/index.js.map +1 -1
  164. package/dist/main.css +393 -90
  165. package/dist/main.css.map +1 -1
  166. package/package.json +30 -23
@@ -1,25 +1,31 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useForm } from "@tanstack/react-form";
3
4
  import { useMemo, useState } from "react";
4
5
  import DOMPurify from "isomorphic-dompurify";
5
6
  import { mockComments, posts } from "../../../data/posts.js";
7
+ import {
8
+ TanStackInputField,
9
+ TanStackTextareaField
10
+ } from "../../../lib/forms/tanstack-field.js";
6
11
  import {
7
12
  Button,
8
13
  Layout,
9
14
  Link,
10
15
  TextTime,
11
16
  UiImage,
12
- Badge,
13
17
  Badges,
14
18
  Icon,
15
- Textarea,
16
- Input,
17
19
  Heading,
18
20
  Text,
19
21
  TextSpan
20
22
  } from "../../primitives/index.js";
23
+ import { FieldGroup } from "../../primitives/forms/field.js";
24
+ import { Form, FormActions } from "../../primitives/forms/form.js";
21
25
  import { ArrowLeft, ArrowRight } from "lucide-react";
22
26
  import EmptyState from "../../blocks/empty-state/EmptyState.js";
27
+ import CategoryBadge from "../../blocks/badges/category-badge.js";
28
+ import ImageGallery from "../../blocks/gallery/image-gallery.js";
23
29
  import { useToast } from "../../primitives/sonner/use-toast.js";
24
30
  import { authors } from "../../../data/authors.js";
25
31
  import {
@@ -28,21 +34,41 @@ import {
28
34
  AvatarImage
29
35
  } from "../../primitives/avatar/avatar.js";
30
36
  import BlogPostCard from "../../blocks/cards/blogpost-card.js";
31
- function BlogPost() {
32
- const postId = 1;
33
- const post = posts.find((p) => p.id === postId);
34
- const postIndex = posts.findIndex((p) => p.id === postId);
37
+ function getReadTime(text) {
38
+ const words = text.trim().split(/\s+/).length;
39
+ return Math.max(1, Math.ceil(words / 200));
40
+ }
41
+ function BlogPost({ slug: propSlug = "the-art-of-slow-living" }) {
42
+ const post = posts.find((p) => p.slug === propSlug);
43
+ const postIndex = posts.findIndex((p) => p.slug === propSlug);
35
44
  const prevPost = postIndex > 0 ? posts[postIndex - 1] : null;
36
45
  const nextPost = postIndex < posts.length - 1 ? posts[postIndex + 1] : null;
37
46
  const isLoggedIn = true;
38
47
  const { toast } = useToast();
39
48
  const [comments, setComments] = useState(mockComments);
40
- const [name, setName] = useState("");
41
- const [text, setText] = useState("");
49
+ const commentForm = useForm({
50
+ defaultValues: { name: "", text: "" },
51
+ onSubmit: ({ value, formApi }) => {
52
+ setComments((prev) => [
53
+ ...prev,
54
+ {
55
+ id: Date.now().toString(),
56
+ author: value.name,
57
+ text: value.text,
58
+ date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
59
+ }
60
+ ]);
61
+ formApi.reset();
62
+ }
63
+ });
42
64
  const [hasLiked, setHasLiked] = useState(false);
43
65
  const [likes, setLikes] = useState(0);
44
66
  const [isGenerating, setIsGenerating] = useState(false);
45
67
  const [isValidate, setIsValidate] = useState(false);
68
+ const readTime = useMemo(
69
+ () => post ? getReadTime(post.content) : 0,
70
+ [post]
71
+ );
46
72
  const relatedPosts = useMemo(() => {
47
73
  if (!post) return [];
48
74
  return posts.filter(
@@ -93,21 +119,6 @@ function BlogPost() {
93
119
  icon: /* @__PURE__ */ jsx(Icon, { icon: "Check" })
94
120
  });
95
121
  };
96
- const handleSubmit = (e) => {
97
- e.preventDefault();
98
- if (!name.trim() || !text.trim()) return;
99
- setComments((prev) => [
100
- ...prev,
101
- {
102
- id: Date.now().toString(),
103
- author: name,
104
- text,
105
- date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
106
- }
107
- ]);
108
- setName("");
109
- setText("");
110
- };
111
122
  const renderBody = (body) => {
112
123
  return body.split("\n\n").map((block, i) => {
113
124
  if (block.startsWith("## ")) {
@@ -158,7 +169,7 @@ function BlogPost() {
158
169
  );
159
170
  });
160
171
  };
161
- const mockedAuthor = authors[0];
172
+ const authorData = authors.find((a) => a.name === post?.author) ?? authors[0];
162
173
  return /* @__PURE__ */ jsxs(Fragment, { children: [
163
174
  /* @__PURE__ */ jsx(Layout, { type: "container", children: /* @__PURE__ */ jsx(Layout.Col1, { className: "sg:w-full sg:h-64 sg:md:h-96 sg:overflow-hidden", children: /* @__PURE__ */ jsx(
164
175
  UiImage,
@@ -174,11 +185,14 @@ function BlogPost() {
174
185
  /* @__PURE__ */ jsxs(TextTime, { size: "sm", foreground: "muted-foreground", children: [
175
186
  post.date,
176
187
  " \xB7 ",
177
- post.author
188
+ post.author,
189
+ " \xB7 ",
190
+ readTime,
191
+ " min read"
178
192
  ] })
179
193
  ] }),
180
194
  /* @__PURE__ */ jsx(Heading, { variant: "h1", className: "sg:mt-2", children: post.title }),
181
- post.categories.length > 0 && /* @__PURE__ */ jsx(Badges, { className: "sg:mt-4", children: post.categories.map((cat) => /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: cat }, cat)) }),
195
+ post.categories.length > 0 && /* @__PURE__ */ jsx(Badges, { className: "sg:mt-4", children: post.categories.map((cat) => /* @__PURE__ */ jsx(CategoryBadge, { category: cat, clickable: true }, cat)) }),
182
196
  /* @__PURE__ */ jsx(
183
197
  Text,
184
198
  {
@@ -228,6 +242,7 @@ function BlogPost() {
228
242
  ] })
229
243
  ] }),
230
244
  /* @__PURE__ */ jsx("div", { className: "sg:mt-8 sg:border-t sg:pt-8", children: renderBody(post.content || "") }),
245
+ post.gallery && post.gallery.length > 0 && /* @__PURE__ */ jsx("div", { className: "sg:mt-12", children: /* @__PURE__ */ jsx(ImageGallery, { images: post.gallery }) }),
231
246
  /* @__PURE__ */ jsx("div", { className: "sg:mt-12 sg:-mx-4 sg:md:-mx-8 sg:rounded-lg sg:overflow-hidden", children: /* @__PURE__ */ jsx(
232
247
  UiImage,
233
248
  {
@@ -238,7 +253,6 @@ function BlogPost() {
238
253
  }
239
254
  ) }),
240
255
  (() => {
241
- const authorData = mockedAuthor;
242
256
  return /* @__PURE__ */ jsx("div", { className: "sg:mt-12 sg:border-t sg:pt-8", children: /* @__PURE__ */ jsxs(
243
257
  Link,
244
258
  {
@@ -307,7 +321,7 @@ function BlogPost() {
307
321
  Link,
308
322
  {
309
323
  variant: "no-decoration",
310
- to: `/posts/${prevPost.id}`,
324
+ to: `/posts/${prevPost.slug}`,
311
325
  className: "sg:group sg:flex sg:flex-col sg:items-start sg:gap-1 sg:rounded-lg sg:border sg:border-border sg:p-4 sg:hover:border-primary sg:transition-colors sg:text-right",
312
326
  "aria-label": `Previous post: ${prevPost.title}`,
313
327
  title: `Previous post: ${prevPost.title}`,
@@ -340,7 +354,7 @@ function BlogPost() {
340
354
  Link,
341
355
  {
342
356
  variant: "no-decoration",
343
- to: `/posts/${nextPost.id}`,
357
+ to: `/posts/${nextPost.slug}`,
344
358
  className: "sg:group sg:flex sg:flex-col sg:items-end sg:gap-1 sg:rounded-lg sg:border sg:border-border sg:p-4 sg:hover:border-primary sg:transition-colors sg:text-right",
345
359
  "aria-label": `Next post: ${nextPost.title}`,
346
360
  title: `Next post: ${nextPost.title}`,
@@ -376,7 +390,14 @@ function BlogPost() {
376
390
  comments.length,
377
391
  ")"
378
392
  ] }),
379
- /* @__PURE__ */ jsx("div", { className: "sg:space-y-4 sg:mb-8", children: comments.map((c) => /* @__PURE__ */ jsxs("div", { className: "sg:rounded-lg sg:bg-accent sg:p-4", children: [
393
+ /* @__PURE__ */ jsx("div", { className: "sg:space-y-4 sg:mb-8", children: comments.length === 0 ? /* @__PURE__ */ jsx(
394
+ EmptyState,
395
+ {
396
+ icon: "MessageSquare",
397
+ title: "No comments yet",
398
+ description: "Be the first to share your thoughts on this post."
399
+ }
400
+ ) : comments.map((c) => /* @__PURE__ */ jsxs("div", { className: "sg:rounded-lg sg:bg-accent sg:p-4", children: [
380
401
  /* @__PURE__ */ jsxs("div", { className: "sg:flex sg:items-center sg:gap-2 sg:mb-1", children: [
381
402
  /* @__PURE__ */ jsx(TextSpan, { fontweight: "medium", size: "sm", children: c.author }),
382
403
  /* @__PURE__ */ jsx(
@@ -391,42 +412,69 @@ function BlogPost() {
391
412
  ] }),
392
413
  /* @__PURE__ */ jsx(Text, { size: "sm", foreground: "muted-foreground", children: c.text })
393
414
  ] }, c.id)) }),
394
- /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "sg:space-y-4", children: [
395
- /* @__PURE__ */ jsx(Heading, { variant: "h4", children: "Leave a comment" }),
396
- /* @__PURE__ */ jsx(
397
- Input,
398
- {
399
- placeholder: "Your name",
400
- value: name,
401
- onChange: (e) => setName(e.target.value),
402
- required: true,
403
- "aria-label": "Your name"
404
- }
405
- ),
406
- /* @__PURE__ */ jsx(
407
- Textarea,
408
- {
409
- placeholder: "Write your comment...",
410
- value: text,
411
- onChange: (e) => setText(e.target.value),
412
- required: true,
413
- "aria-label": "Your comment"
414
- }
415
- ),
416
- /* @__PURE__ */ jsx(Button, { type: "submit", children: "Post comment" })
417
- ] })
415
+ /* @__PURE__ */ jsxs(
416
+ Form,
417
+ {
418
+ className: "sg:gap-4",
419
+ onSubmit: (event) => {
420
+ event.preventDefault();
421
+ event.stopPropagation();
422
+ void commentForm.handleSubmit();
423
+ },
424
+ children: [
425
+ /* @__PURE__ */ jsx(Heading, { variant: "h4", children: "Leave a comment" }),
426
+ /* @__PURE__ */ jsxs(FieldGroup, { className: "sg:gap-4", children: [
427
+ /* @__PURE__ */ jsx(
428
+ TanStackInputField,
429
+ {
430
+ formApi: commentForm,
431
+ name: "name",
432
+ label: "Your name",
433
+ labelClassName: "sg:sr-only",
434
+ placeholder: "Your name",
435
+ validators: {
436
+ onChange: ({ value }) => value.trim() ? void 0 : "Name is required"
437
+ }
438
+ }
439
+ ),
440
+ /* @__PURE__ */ jsx(
441
+ TanStackTextareaField,
442
+ {
443
+ formApi: commentForm,
444
+ name: "text",
445
+ label: "Your comment",
446
+ labelClassName: "sg:sr-only",
447
+ placeholder: "Write your comment...",
448
+ validators: {
449
+ onChange: ({ value }) => value.trim() ? void 0 : "Comment is required"
450
+ }
451
+ }
452
+ )
453
+ ] }),
454
+ /* @__PURE__ */ jsx(FormActions, { children: /* @__PURE__ */ jsx(
455
+ commentForm.Subscribe,
456
+ {
457
+ selector: (state) => [state.canSubmit, state.isSubmitting],
458
+ children: ([canSubmit, isSubmitting]) => /* @__PURE__ */ jsx(Button, { type: "submit", disabled: !canSubmit || isSubmitting, children: "Post comment" })
459
+ }
460
+ ) })
461
+ ]
462
+ }
463
+ )
418
464
  ] }),
419
465
  relatedPosts.length > 0 && /* @__PURE__ */ jsxs("section", { className: "sg:mt-16 sg:border-t sg:pt-8", children: [
420
466
  /* @__PURE__ */ jsx(Heading, { variant: "h3", className: "sg:mb-6", children: "Related Posts" }),
421
- /* @__PURE__ */ jsx("div", { className: "sg:grid sg:gap-6 sg:sm:grid-cols-2 lg:grid-cols-3", children: relatedPosts.map((p) => /* @__PURE__ */ jsx(
467
+ /* @__PURE__ */ jsx("div", { className: "sg:grid sg:gap-6 sg:sm:grid-cols-2 lg:sg:grid-cols-3", children: relatedPosts.map((p) => /* @__PURE__ */ jsx(
422
468
  BlogPostCard,
423
469
  {
424
- id: post.id,
425
- image: post.primaryImage,
426
- categories: post.categories,
427
- date: post.date,
428
- title: post.title,
429
- excerpt: post.excerpt
470
+ id: p.id,
471
+ slug: p.slug,
472
+ image: p.primaryImage,
473
+ categories: p.categories,
474
+ date: p.date,
475
+ title: p.title,
476
+ excerpt: p.excerpt,
477
+ clickableCategories: true
430
478
  },
431
479
  p.id
432
480
  )) })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/pages/blogpost/blogpost.tsx"],"sourcesContent":["\"use client\";\nimport { useMemo, useState } from \"react\";\nimport DOMPurify from \"isomorphic-dompurify\";\nimport { mockComments, posts, type Comment } from \"../../../data/posts\";\nimport {\n Button,\n Layout,\n Link,\n TextTime,\n UiImage,\n Badge,\n Badges,\n Icon,\n Textarea,\n Input,\n Heading,\n Text,\n TextSpan,\n} from \"../../primitives/index\";\nimport { ArrowLeft, ArrowRight } from \"lucide-react\";\nimport EmptyState from \"../../blocks/empty-state/EmptyState\";\nimport { useToast } from \"../../primitives/sonner/use-toast\";\nimport { authors } from \"../../../data/authors\";\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"../../primitives/avatar/avatar\";\nimport BlogPostCard from \"../../blocks/cards/blogpost-card\";\n\nfunction BlogPost() {\n const postId = 1; // In a real app, this would come from the URL params\n\n const post = posts.find((p) => p.id === postId);\n const postIndex = posts.findIndex((p) => p.id === postId);\n const prevPost = postIndex > 0 ? posts[postIndex - 1] : null;\n const nextPost = postIndex < posts.length - 1 ? posts[postIndex + 1] : null;\n\n const isLoggedIn = true;\n const { toast } = useToast();\n\n const [comments, setComments] = useState<Comment[]>(mockComments);\n const [name, setName] = useState(\"\");\n const [text, setText] = useState(\"\");\n\n const [hasLiked, setHasLiked] = useState(false);\n const [likes, setLikes] = useState(0);\n\n const [isGenerating, setIsGenerating] = useState(false);\n const [isValidate, setIsValidate] = useState(false);\n\n const relatedPosts = useMemo(() => {\n if (!post) return [];\n return posts\n .filter(\n (p) =>\n p.id !== post.id &&\n p.categories.some((c) => post.categories.includes(c)),\n )\n .slice(0, 3);\n }, [post]);\n\n if (!post) {\n return (\n <Layout>\n <Layout.Col1 hideDiv>\n <EmptyState\n icon=\"BookOpen\"\n title=\"Post not found\"\n description=\"This post may have been removed or the link is incorrect.\"\n actionLabel=\"Browse all posts\"\n actionTo=\"/posts\"\n className=\"min-h-[60vh]\"\n />\n </Layout.Col1>\n </Layout>\n );\n }\n\n const handleLike = () => {\n if (!isLoggedIn) return;\n if (hasLiked) {\n setLikes((l) => l - 1);\n setHasLiked(false);\n } else {\n setLikes((l) => l + 1);\n setHasLiked(true);\n }\n };\n\n const handleGenerateImage = () => {\n setIsGenerating(true);\n setTimeout(() => {\n setIsGenerating(false);\n }, 1000);\n toast.message(\"Generating image…\", {\n description: \"A new AI image is being created for this post.\",\n dismissible: true,\n icon: <Icon icon=\"ImagePlus\" />,\n });\n };\n\n const handleValidate = () => {\n setIsValidate(true);\n setTimeout(() => {\n setIsValidate(false);\n }, 1000);\n toast.message(\"Post validated\", {\n description: \"Content has been reviewed.\",\n icon: <Icon icon=\"Check\" />,\n });\n };\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (!name.trim() || !text.trim()) return;\n setComments((prev) => [\n ...prev,\n {\n id: Date.now().toString(),\n author: name,\n text,\n date: new Date().toISOString().slice(0, 10),\n },\n ]);\n setName(\"\");\n setText(\"\");\n };\n\n // Simple markdown-ish rendering for content\n const renderBody = (body: string) => {\n return body.split(\"\\n\\n\").map((block, i) => {\n if (block.startsWith(\"## \")) {\n return (\n <Heading variant=\"h2\" key={i} className=\"sg:mt-8 sg:mb-4\">\n {block.replace(\"## \", \"\")}\n </Heading>\n );\n }\n if (block.startsWith(\"1. \") || block.startsWith(\"- \")) {\n const items = block\n .split(\"\\n\")\n .map((line) => line.replace(/^(\\d+\\.\\s|-\\s)/, \"\"));\n const isOrdered = block.startsWith(\"1.\");\n const Tag = isOrdered ? \"ol\" : \"ul\";\n\n return (\n <Tag\n key={i}\n className={`sg:my-4 sg:ml-6 sg:space-y-2 ${isOrdered ? \"sg:list-decimal\" : \"sg:list-disc\"}`}\n >\n {items.map((item, j) => (\n <li\n key={j}\n className=\"sg:text-muted-foreground\"\n dangerouslySetInnerHTML={{\n __html: DOMPurify.sanitize(\n item.replace(\n /\\*\\*(.*?)\\*\\*/g,\n '<strong class=\"text-foreground\">$1</strong>',\n ),\n ),\n }}\n />\n ))}\n </Tag>\n );\n }\n\n return (\n <Text\n key={i}\n className=\"sg:my-4\"\n foreground=\"muted-foreground\"\n dangerouslySetInnerHTML={{\n __html: DOMPurify.sanitize(\n block.replace(\n /\\*\\*(.*?)\\*\\*/g,\n '<strong class=\"sg:text-foreground\">$1</strong>',\n ),\n ),\n }}\n />\n );\n });\n };\n\n // Using a mocked author for now as PostGetDto lacks author info\n const mockedAuthor = authors[0];\n\n return (\n <>\n <Layout type=\"container\">\n <Layout.Col1 className=\"sg:w-full sg:h-64 sg:md:h-96 sg:overflow-hidden\">\n <UiImage\n src={post.primaryImage || post.originalImage || \"\"}\n alt={post.title}\n className=\"sg:w-full sg:h-full sg:object-cover\"\n />\n </Layout.Col1>\n </Layout>\n <Layout type=\"col\" className=\"sg:py-12\" as=\"article\">\n <Layout.Col1 hideDiv>\n <div className=\"sg:flex sg:flex-col sg:gap-6 sg:items-start\">\n <Link to=\"/posts\" iconStart=\"ArrowLeft\">\n Back to all posts\n </Link>\n\n <TextTime size=\"sm\" foreground=\"muted-foreground\">\n {post.date} Β· {post.author}\n </TextTime>\n </div>\n\n <Heading variant=\"h1\" className=\"sg:mt-2\">\n {post.title}\n </Heading>\n\n {post.categories.length > 0 && (\n <Badges className=\"sg:mt-4\">\n {post.categories.map((cat) => (\n <Badge key={cat} variant=\"secondary\">\n {cat}\n </Badge>\n ))}\n </Badges>\n )}\n\n <Text\n size=\"lg\"\n foreground=\"muted-foreground\"\n className=\"sg:mt-4 sg:italic\"\n >\n {post.excerpt}\n </Text>\n\n {/* Like + admin actions bar */}\n <div className=\"sg:mt-6 sg:flex sg:items-center sg:gap-3 sg:flex-wrap\">\n <Button\n onClick={handleLike}\n disabled={!isLoggedIn}\n variant=\"outline\"\n size=\"sm\"\n iconStart=\"Heart\"\n iconStartFill={hasLiked}\n title={\n isLoggedIn\n ? hasLiked\n ? \"Unlike\"\n : \"Like this post\"\n : \"Log in to like\"\n }\n >\n {likes}\n </Button>\n\n {isLoggedIn && (\n <>\n <Button\n variant=\"outline\"\n iconStart=\"ImagePlus\"\n size=\"sm\"\n onClick={handleGenerateImage}\n loading={isGenerating}\n >\n Generate image\n </Button>\n <Button\n variant=\"outline\"\n iconStart=\"ShieldCheck\"\n size=\"sm\"\n onClick={handleValidate}\n loading={isValidate}\n >\n Validate\n </Button>\n </>\n )}\n </div>\n\n <div className=\"sg:mt-8 sg:border-t sg:pt-8\">\n {renderBody(post.content || \"\")}\n </div>\n\n {/* Secondary image */}\n <div className=\"sg:mt-12 sg:-mx-4 sg:md:-mx-8 sg:rounded-lg sg:overflow-hidden\">\n <UiImage\n src={(post.originalImage || \"\") + \"&q=80&crop=entropy\"}\n alt={`Visual for ${post.title}`}\n className=\"sg:w-full sg:h-56 sg:md:h-72 sg:object-cover\"\n loading=\"lazy\"\n />\n </div>\n\n {/* Author card */}\n {(() => {\n const authorData = mockedAuthor;\n return (\n <div className=\"sg:mt-12 sg:border-t sg:pt-8\">\n <Link\n variant=\"no-decoration\"\n to={`/extras/authors/${authorData.slug}`}\n className=\"sg:group sg:flex sg:items-start sg:gap-4 sg:rounded-lg sg:border sg:bg-card sg:p-5 sg:hover:shadow-md sg:hover:border-primary/40 sg:transition-all\"\n >\n <Avatar className=\"sg:h-14 sg:w-14 sg:mt-0.5\">\n <AvatarImage\n src={authorData.avatar}\n alt={authorData.name}\n />\n <AvatarFallback>\n {authorData.name\n .split(\" \")\n .map((n) => n[0])\n .join(\"\")}\n </AvatarFallback>\n </Avatar>\n <div className=\"sg:min-w-0\">\n <TextSpan\n size=\"xs\"\n foreground=\"muted-foreground\"\n className=\"sg:uppercase sg:tracking-wider sg:mb-1\"\n >\n Written by\n </TextSpan>\n <Text\n size=\"sm\"\n fontweight=\"semibold\"\n className=\"sg:font-display sg:group-hover:text-primary sg:transition-colors\"\n >\n {authorData.name}\n </Text>\n <Text\n size=\"sm\"\n foreground=\"muted-foreground\"\n className=\"sg:mt-0.5\"\n >\n {authorData.role} Β· {authorData.location}\n </Text>\n <Text\n size=\"sm\"\n foreground=\"muted-foreground\"\n className=\"sg:mt-2 sg:line-clamp-2\"\n >\n {authorData.bio}\n </Text>\n </div>\n </Link>\n </div>\n );\n })()}\n\n {/* Prev / Next navigation */}\n <nav className=\"sg:mt-12 sg:border-t sg:pt-8 sg:grid sg:grid-cols-2 sg:gap-4\">\n {prevPost ? (\n <Link\n variant=\"no-decoration\"\n to={`/posts/${prevPost.id}`}\n className=\"sg:group sg:flex sg:flex-col sg:items-start sg:gap-1 sg:rounded-lg sg:border sg:border-border sg:p-4 sg:hover:border-primary sg:transition-colors sg:text-right\"\n aria-label={`Previous post: ${prevPost.title}`}\n title={`Previous post: ${prevPost.title}`}\n >\n <TextSpan\n size=\"xs\"\n foreground=\"muted-foreground\"\n className=\"sg:flex sg:items-center sg:gap-1\"\n >\n <ArrowLeft className=\"h-3 w-3\" /> Previous\n </TextSpan>\n <TextSpan\n size=\"sm\"\n fontweight=\"medium\"\n className=\"sg:group-hover:text-primary sg:transition-colors sg:line-clamp-1 sg:text-start\"\n >\n {prevPost.title}\n </TextSpan>\n </Link>\n ) : (\n <div />\n )}\n {nextPost ? (\n <Link\n variant=\"no-decoration\"\n to={`/posts/${nextPost.id}`}\n className=\"sg:group sg:flex sg:flex-col sg:items-end sg:gap-1 sg:rounded-lg sg:border sg:border-border sg:p-4 sg:hover:border-primary sg:transition-colors sg:text-right\"\n aria-label={`Next post: ${nextPost.title}`}\n title={`Next post: ${nextPost.title}`}\n >\n <TextSpan\n size=\"xs\"\n foreground=\"muted-foreground\"\n className=\"sg:flex sg:items-center sg:gap-1\"\n >\n Next <ArrowRight className=\"h-3 w-3\" />\n </TextSpan>\n <TextSpan\n size=\"sm\"\n fontweight=\"medium\"\n className=\"sg:group-hover:text-primary sg:transition-colors sg:line-clamp-1\"\n >\n {nextPost.title}\n </TextSpan>\n </Link>\n ) : (\n <div />\n )}\n </nav>\n\n {/* Comments */}\n <section className=\"sg:mt-16 sg:border-t sg:pt-8\">\n <Heading variant=\"h3\" className=\"sg:mb-6\">\n Comments ({comments.length})\n </Heading>\n\n <div className=\"sg:space-y-4 sg:mb-8\">\n {comments.map((c) => (\n <div key={c.id} className=\"sg:rounded-lg sg:bg-accent sg:p-4\">\n <div className=\"sg:flex sg:items-center sg:gap-2 sg:mb-1\">\n <TextSpan fontweight=\"medium\" size=\"sm\">\n {c.author}\n </TextSpan>\n <TextSpan\n fontweight=\"medium\"\n size=\"xs\"\n foreground=\"muted-foreground\"\n >\n {c.date}\n </TextSpan>\n </div>\n <Text size=\"sm\" foreground=\"muted-foreground\">\n {c.text}\n </Text>\n </div>\n ))}\n </div>\n\n <form onSubmit={handleSubmit} className=\"sg:space-y-4\">\n <Heading variant=\"h4\">Leave a comment</Heading>\n <Input\n placeholder=\"Your name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n required\n aria-label=\"Your name\"\n />\n <Textarea\n placeholder=\"Write your comment...\"\n value={text}\n onChange={(e) => setText(e.target.value)}\n required\n aria-label=\"Your comment\"\n />\n <Button type=\"submit\">Post comment</Button>\n </form>\n </section>\n\n {/* Related Posts */}\n {relatedPosts.length > 0 && (\n <section className=\"sg:mt-16 sg:border-t sg:pt-8\">\n <Heading variant=\"h3\" className=\"sg:mb-6\">\n Related Posts\n </Heading>\n <div className=\"sg:grid sg:gap-6 sg:sm:grid-cols-2 lg:grid-cols-3\">\n {relatedPosts.map((p) => (\n <BlogPostCard\n key={p.id}\n id={post.id}\n image={post.primaryImage}\n categories={post.categories}\n date={post.date}\n title={post.title}\n excerpt={post.excerpt}\n />\n ))}\n </div>\n </section>\n )}\n </Layout.Col1>\n </Layout>\n </>\n );\n}\n\nexport { BlogPost };\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/pages/blogpost/blogpost.tsx"],"sourcesContent":["\"use client\";\nimport { useForm } from \"@tanstack/react-form\";\nimport { useMemo, useState } from \"react\";\nimport DOMPurify from \"isomorphic-dompurify\";\nimport { mockComments, posts, type Comment } from \"../../../data/posts\";\nimport {\n\tTanStackInputField,\n\tTanStackTextareaField,\n} from \"../../../lib/forms/tanstack-field\";\nimport {\n Button,\n Layout,\n Link,\n TextTime,\n UiImage,\n Badges,\n Icon,\n Heading,\n Text,\n TextSpan,\n} from \"../../primitives/index\";\nimport { FieldGroup } from \"../../primitives/forms/field\";\nimport { Form, FormActions } from \"../../primitives/forms/form\";\nimport { ArrowLeft, ArrowRight } from \"lucide-react\";\nimport EmptyState from \"../../blocks/empty-state/EmptyState\";\nimport CategoryBadge from \"../../blocks/badges/category-badge\";\nimport ImageGallery from \"../../blocks/gallery/image-gallery\";\nimport { useToast } from \"../../primitives/sonner/use-toast\";\nimport { authors } from \"../../../data/authors\";\nimport {\n Avatar,\n AvatarFallback,\n AvatarImage,\n} from \"../../primitives/avatar/avatar\";\nimport BlogPostCard from \"../../blocks/cards/blogpost-card\";\n\nfunction getReadTime(text: string): number {\n const words = text.trim().split(/\\s+/).length;\n return Math.max(1, Math.ceil(words / 200));\n}\n\ninterface BlogPostProps {\n slug?: string;\n}\n\nfunction BlogPost({ slug: propSlug = \"the-art-of-slow-living\" }: BlogPostProps) {\n const post = posts.find((p) => p.slug === propSlug);\n const postIndex = posts.findIndex((p) => p.slug === propSlug);\n const prevPost = postIndex > 0 ? posts[postIndex - 1] : null;\n const nextPost = postIndex < posts.length - 1 ? posts[postIndex + 1] : null;\n\n const isLoggedIn = true;\n const { toast } = useToast();\n\n const [comments, setComments] = useState<Comment[]>(mockComments);\n\n const commentForm = useForm({\n defaultValues: { name: \"\", text: \"\" },\n onSubmit: ({ value, formApi }) => {\n setComments((prev) => [\n ...prev,\n {\n id: Date.now().toString(),\n author: value.name,\n text: value.text,\n date: new Date().toISOString().slice(0, 10),\n },\n ]);\n formApi.reset();\n },\n });\n\n const [hasLiked, setHasLiked] = useState(false);\n const [likes, setLikes] = useState(0);\n\n const [isGenerating, setIsGenerating] = useState(false);\n const [isValidate, setIsValidate] = useState(false);\n\n const readTime = useMemo(\n () => (post ? getReadTime(post.content) : 0),\n [post],\n );\n\n const relatedPosts = useMemo(() => {\n if (!post) return [];\n return posts\n .filter(\n (p) =>\n p.id !== post.id &&\n p.categories.some((c) => post.categories.includes(c)),\n )\n .slice(0, 3);\n }, [post]);\n\n if (!post) {\n return (\n <Layout>\n <Layout.Col1 hideDiv>\n <EmptyState\n icon=\"BookOpen\"\n title=\"Post not found\"\n description=\"This post may have been removed or the link is incorrect.\"\n actionLabel=\"Browse all posts\"\n actionTo=\"/posts\"\n className=\"min-h-[60vh]\"\n />\n </Layout.Col1>\n </Layout>\n );\n }\n\n const handleLike = () => {\n if (!isLoggedIn) return;\n if (hasLiked) {\n setLikes((l) => l - 1);\n setHasLiked(false);\n } else {\n setLikes((l) => l + 1);\n setHasLiked(true);\n }\n };\n\n const handleGenerateImage = () => {\n setIsGenerating(true);\n setTimeout(() => {\n setIsGenerating(false);\n }, 1000);\n toast.message(\"Generating image…\", {\n description: \"A new AI image is being created for this post.\",\n dismissible: true,\n icon: <Icon icon=\"ImagePlus\" />,\n });\n };\n\n const handleValidate = () => {\n setIsValidate(true);\n setTimeout(() => {\n setIsValidate(false);\n }, 1000);\n toast.message(\"Post validated\", {\n description: \"Content has been reviewed.\",\n icon: <Icon icon=\"Check\" />,\n });\n };\n\n // Simple markdown-ish rendering for content\n const renderBody = (body: string) => {\n return body.split(\"\\n\\n\").map((block, i) => {\n if (block.startsWith(\"## \")) {\n return (\n <Heading variant=\"h2\" key={i} className=\"sg:mt-8 sg:mb-4\">\n {block.replace(\"## \", \"\")}\n </Heading>\n );\n }\n if (block.startsWith(\"1. \") || block.startsWith(\"- \")) {\n const items = block\n .split(\"\\n\")\n .map((line) => line.replace(/^(\\d+\\.\\s|-\\s)/, \"\"));\n const isOrdered = block.startsWith(\"1.\");\n const Tag = isOrdered ? \"ol\" : \"ul\";\n\n return (\n <Tag\n key={i}\n className={`sg:my-4 sg:ml-6 sg:space-y-2 ${isOrdered ? \"sg:list-decimal\" : \"sg:list-disc\"}`}\n >\n {items.map((item, j) => (\n <li\n key={j}\n className=\"sg:text-muted-foreground\"\n dangerouslySetInnerHTML={{\n __html: DOMPurify.sanitize(\n item.replace(\n /\\*\\*(.*?)\\*\\*/g,\n '<strong class=\"text-foreground\">$1</strong>',\n ),\n ),\n }}\n />\n ))}\n </Tag>\n );\n }\n\n return (\n <Text\n key={i}\n className=\"sg:my-4\"\n foreground=\"muted-foreground\"\n dangerouslySetInnerHTML={{\n __html: DOMPurify.sanitize(\n block.replace(\n /\\*\\*(.*?)\\*\\*/g,\n '<strong class=\"sg:text-foreground\">$1</strong>',\n ),\n ),\n }}\n />\n );\n });\n };\n\n // Resolve author from post data\n const authorData = authors.find((a) => a.name === post?.author) ?? authors[0];\n\n return (\n <>\n <Layout type=\"container\">\n <Layout.Col1 className=\"sg:w-full sg:h-64 sg:md:h-96 sg:overflow-hidden\">\n <UiImage\n src={post.primaryImage || post.originalImage || \"\"}\n alt={post.title}\n className=\"sg:w-full sg:h-full sg:object-cover\"\n />\n </Layout.Col1>\n </Layout>\n <Layout type=\"col\" className=\"sg:py-12\" as=\"article\">\n <Layout.Col1 hideDiv>\n <div className=\"sg:flex sg:flex-col sg:gap-6 sg:items-start\">\n <Link to=\"/posts\" iconStart=\"ArrowLeft\">\n Back to all posts\n </Link>\n\n <TextTime size=\"sm\" foreground=\"muted-foreground\">\n {post.date} Β· {post.author} Β· {readTime} min read\n </TextTime>\n </div>\n\n <Heading variant=\"h1\" className=\"sg:mt-2\">\n {post.title}\n </Heading>\n\n {post.categories.length > 0 && (\n <Badges className=\"sg:mt-4\">\n {post.categories.map((cat) => (\n <CategoryBadge key={cat} category={cat} clickable />\n ))}\n </Badges>\n )}\n\n <Text\n size=\"lg\"\n foreground=\"muted-foreground\"\n className=\"sg:mt-4 sg:italic\"\n >\n {post.excerpt}\n </Text>\n\n {/* Like + admin actions bar */}\n <div className=\"sg:mt-6 sg:flex sg:items-center sg:gap-3 sg:flex-wrap\">\n <Button\n onClick={handleLike}\n disabled={!isLoggedIn}\n variant=\"outline\"\n size=\"sm\"\n iconStart=\"Heart\"\n iconStartFill={hasLiked}\n title={\n isLoggedIn\n ? hasLiked\n ? \"Unlike\"\n : \"Like this post\"\n : \"Log in to like\"\n }\n >\n {likes}\n </Button>\n\n {isLoggedIn && (\n <>\n <Button\n variant=\"outline\"\n iconStart=\"ImagePlus\"\n size=\"sm\"\n onClick={handleGenerateImage}\n loading={isGenerating}\n >\n Generate image\n </Button>\n <Button\n variant=\"outline\"\n iconStart=\"ShieldCheck\"\n size=\"sm\"\n onClick={handleValidate}\n loading={isValidate}\n >\n Validate\n </Button>\n </>\n )}\n </div>\n\n <div className=\"sg:mt-8 sg:border-t sg:pt-8\">\n {renderBody(post.content || \"\")}\n </div>\n\n {post.gallery && post.gallery.length > 0 && (\n <div className=\"sg:mt-12\">\n <ImageGallery images={post.gallery} />\n </div>\n )}\n\n {/* Secondary image */}\n <div className=\"sg:mt-12 sg:-mx-4 sg:md:-mx-8 sg:rounded-lg sg:overflow-hidden\">\n <UiImage\n src={(post.originalImage || \"\") + \"&q=80&crop=entropy\"}\n alt={`Visual for ${post.title}`}\n className=\"sg:w-full sg:h-56 sg:md:h-72 sg:object-cover\"\n loading=\"lazy\"\n />\n </div>\n\n {/* Author card */}\n {(() => {\n return (\n <div className=\"sg:mt-12 sg:border-t sg:pt-8\">\n <Link\n variant=\"no-decoration\"\n to={`/extras/authors/${authorData.slug}`}\n className=\"sg:group sg:flex sg:items-start sg:gap-4 sg:rounded-lg sg:border sg:bg-card sg:p-5 sg:hover:shadow-md sg:hover:border-primary/40 sg:transition-all\"\n >\n <Avatar className=\"sg:h-14 sg:w-14 sg:mt-0.5\">\n <AvatarImage\n src={authorData.avatar}\n alt={authorData.name}\n />\n <AvatarFallback>\n {authorData.name\n .split(\" \")\n .map((n) => n[0])\n .join(\"\")}\n </AvatarFallback>\n </Avatar>\n <div className=\"sg:min-w-0\">\n <TextSpan\n size=\"xs\"\n foreground=\"muted-foreground\"\n className=\"sg:uppercase sg:tracking-wider sg:mb-1\"\n >\n Written by\n </TextSpan>\n <Text\n size=\"sm\"\n fontweight=\"semibold\"\n className=\"sg:font-display sg:group-hover:text-primary sg:transition-colors\"\n >\n {authorData.name}\n </Text>\n <Text\n size=\"sm\"\n foreground=\"muted-foreground\"\n className=\"sg:mt-0.5\"\n >\n {authorData.role} Β· {authorData.location}\n </Text>\n <Text\n size=\"sm\"\n foreground=\"muted-foreground\"\n className=\"sg:mt-2 sg:line-clamp-2\"\n >\n {authorData.bio}\n </Text>\n </div>\n </Link>\n </div>\n );\n })()}\n\n {/* Prev / Next navigation */}\n <nav className=\"sg:mt-12 sg:border-t sg:pt-8 sg:grid sg:grid-cols-2 sg:gap-4\">\n {prevPost ? (\n <Link\n variant=\"no-decoration\"\n to={`/posts/${prevPost.slug}`}\n className=\"sg:group sg:flex sg:flex-col sg:items-start sg:gap-1 sg:rounded-lg sg:border sg:border-border sg:p-4 sg:hover:border-primary sg:transition-colors sg:text-right\"\n aria-label={`Previous post: ${prevPost.title}`}\n title={`Previous post: ${prevPost.title}`}\n >\n <TextSpan\n size=\"xs\"\n foreground=\"muted-foreground\"\n className=\"sg:flex sg:items-center sg:gap-1\"\n >\n <ArrowLeft className=\"h-3 w-3\" /> Previous\n </TextSpan>\n <TextSpan\n size=\"sm\"\n fontweight=\"medium\"\n className=\"sg:group-hover:text-primary sg:transition-colors sg:line-clamp-1 sg:text-start\"\n >\n {prevPost.title}\n </TextSpan>\n </Link>\n ) : (\n <div />\n )}\n {nextPost ? (\n <Link\n variant=\"no-decoration\"\n to={`/posts/${nextPost.slug}`}\n className=\"sg:group sg:flex sg:flex-col sg:items-end sg:gap-1 sg:rounded-lg sg:border sg:border-border sg:p-4 sg:hover:border-primary sg:transition-colors sg:text-right\"\n aria-label={`Next post: ${nextPost.title}`}\n title={`Next post: ${nextPost.title}`}\n >\n <TextSpan\n size=\"xs\"\n foreground=\"muted-foreground\"\n className=\"sg:flex sg:items-center sg:gap-1\"\n >\n Next <ArrowRight className=\"h-3 w-3\" />\n </TextSpan>\n <TextSpan\n size=\"sm\"\n fontweight=\"medium\"\n className=\"sg:group-hover:text-primary sg:transition-colors sg:line-clamp-1\"\n >\n {nextPost.title}\n </TextSpan>\n </Link>\n ) : (\n <div />\n )}\n </nav>\n\n {/* Comments */}\n <section className=\"sg:mt-16 sg:border-t sg:pt-8\">\n <Heading variant=\"h3\" className=\"sg:mb-6\">\n Comments ({comments.length})\n </Heading>\n\n <div className=\"sg:space-y-4 sg:mb-8\">\n {comments.length === 0 ? (\n <EmptyState\n icon=\"MessageSquare\"\n title=\"No comments yet\"\n description=\"Be the first to share your thoughts on this post.\"\n />\n ) : (\n comments.map((c) => (\n <div key={c.id} className=\"sg:rounded-lg sg:bg-accent sg:p-4\">\n <div className=\"sg:flex sg:items-center sg:gap-2 sg:mb-1\">\n <TextSpan fontweight=\"medium\" size=\"sm\">\n {c.author}\n </TextSpan>\n <TextSpan\n fontweight=\"medium\"\n size=\"xs\"\n foreground=\"muted-foreground\"\n >\n {c.date}\n </TextSpan>\n </div>\n <Text size=\"sm\" foreground=\"muted-foreground\">\n {c.text}\n </Text>\n </div>\n ))\n )}\n </div>\n\n <Form\n className=\"sg:gap-4\"\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void commentForm.handleSubmit();\n }}\n >\n <Heading variant=\"h4\">Leave a comment</Heading>\n <FieldGroup className=\"sg:gap-4\">\n <TanStackInputField\n formApi={commentForm}\n name=\"name\"\n label=\"Your name\"\n labelClassName=\"sg:sr-only\"\n placeholder=\"Your name\"\n validators={{\n onChange: ({ value }: { value: string }) =>\n value.trim() ? undefined : \"Name is required\",\n }}\n />\n <TanStackTextareaField\n formApi={commentForm}\n name=\"text\"\n label=\"Your comment\"\n labelClassName=\"sg:sr-only\"\n placeholder=\"Write your comment...\"\n validators={{\n onChange: ({ value }: { value: string }) =>\n value.trim() ? undefined : \"Comment is required\",\n }}\n />\n </FieldGroup>\n <FormActions>\n <commentForm.Subscribe\n selector={(state) => [state.canSubmit, state.isSubmitting]}\n >\n {([canSubmit, isSubmitting]) => (\n <Button type=\"submit\" disabled={!canSubmit || isSubmitting}>\n Post comment\n </Button>\n )}\n </commentForm.Subscribe>\n </FormActions>\n </Form>\n </section>\n\n {/* Related Posts */}\n {relatedPosts.length > 0 && (\n <section className=\"sg:mt-16 sg:border-t sg:pt-8\">\n <Heading variant=\"h3\" className=\"sg:mb-6\">\n Related Posts\n </Heading>\n <div className=\"sg:grid sg:gap-6 sg:sm:grid-cols-2 lg:sg:grid-cols-3\">\n {relatedPosts.map((p) => (\n <BlogPostCard\n key={p.id}\n id={p.id}\n slug={p.slug}\n image={p.primaryImage}\n categories={p.categories}\n date={p.date}\n title={p.title}\n excerpt={p.excerpt}\n clickableCategories\n />\n ))}\n </div>\n </section>\n )}\n </Layout.Col1>\n </Layout>\n </>\n );\n}\n\nexport { BlogPost };\n"],"mappings":"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","names":[]}
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+
3
+ declare function CategoriesPage(): React.JSX.Element;
4
+
5
+ export { CategoriesPage };
@@ -0,0 +1,33 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useMemo } from "react";
4
+ import { motion } from "framer-motion";
5
+ import { Layout, Heading, Text } from "../../primitives/index.js";
6
+ import { CategoryCard } from "../../blocks/directory/category-card.js";
7
+ import { posts } from "../../../data/posts.js";
8
+ function CategoriesPage() {
9
+ const categories = useMemo(() => {
10
+ const map = /* @__PURE__ */ new Map();
11
+ posts.forEach(
12
+ (p) => p.categories.forEach((c) => map.set(c, (map.get(c) || 0) + 1))
13
+ );
14
+ return Array.from(map.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([name, count]) => ({ name, count }));
15
+ }, []);
16
+ return /* @__PURE__ */ jsx(Layout, { type: "col", className: "sg:py-16", children: /* @__PURE__ */ jsx(Layout.Col1, { hideDiv: true, className: "sg:max-w-3xl", children: /* @__PURE__ */ jsxs(
17
+ motion.div,
18
+ {
19
+ initial: { opacity: 1, y: 20 },
20
+ animate: { opacity: 1, y: 0 },
21
+ transition: { duration: 0.3 },
22
+ children: [
23
+ /* @__PURE__ */ jsx(Heading, { variant: "h1", className: "sg:mb-2", children: "Categories" }),
24
+ /* @__PURE__ */ jsx(Text, { foreground: "muted-foreground", className: "sg:mb-10", children: "Browse posts by topic." }),
25
+ /* @__PURE__ */ jsx("div", { className: "sg:grid sg:gap-4 sm:sg:grid-cols-2", children: categories.map((cat) => /* @__PURE__ */ jsx(CategoryCard, { name: cat.name, count: cat.count }, cat.name)) })
26
+ ]
27
+ }
28
+ ) }) });
29
+ }
30
+ export {
31
+ CategoriesPage
32
+ };
33
+ //# sourceMappingURL=categories-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/components/pages/categories/categories-page.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport { useMemo } from \"react\";\r\nimport { motion } from \"framer-motion\";\r\nimport { Layout, Heading, Text } from \"../../primitives/index\";\r\nimport { CategoryCard } from \"../../blocks/directory/category-card\";\r\nimport { posts } from \"../../../data/posts\";\r\n\r\nexport function CategoriesPage() {\r\n\tconst categories = useMemo(() => {\r\n\t\tconst map = new Map<string, number>();\r\n\t\tposts.forEach((p) =>\r\n\t\t\tp.categories.forEach((c) => map.set(c, (map.get(c) || 0) + 1)),\r\n\t\t);\r\n\t\treturn Array.from(map.entries())\r\n\t\t\t.sort((a, b) => a[0].localeCompare(b[0]))\r\n\t\t\t.map(([name, count]) => ({ name, count }));\r\n\t}, []);\r\n\r\n\treturn (\r\n\t\t<Layout type=\"col\" className=\"sg:py-16\">\r\n\t\t\t<Layout.Col1 hideDiv className=\"sg:max-w-3xl\">\r\n\t\t\t\t<motion.div\r\n\t\t\t\t\tinitial={{ opacity: 1, y: 20 }}\r\n\t\t\t\t\tanimate={{ opacity: 1, y: 0 }}\r\n\t\t\t\t\ttransition={{ duration: 0.3 }}\r\n\t\t\t\t>\r\n\t\t\t\t\t<Heading variant=\"h1\" className=\"sg:mb-2\">\r\n\t\t\t\t\t\tCategories\r\n\t\t\t\t\t</Heading>\r\n\t\t\t\t\t<Text foreground=\"muted-foreground\" className=\"sg:mb-10\">\r\n\t\t\t\t\t\tBrowse posts by topic.\r\n\t\t\t\t\t</Text>\r\n\t\t\t\t\t<div className=\"sg:grid sg:gap-4 sm:sg:grid-cols-2\">\r\n\t\t\t\t\t\t{categories.map((cat) => (\r\n\t\t\t\t\t\t\t<CategoryCard key={cat.name} name={cat.name} count={cat.count} />\r\n\t\t\t\t\t\t))}\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</motion.div>\r\n\t\t\t</Layout.Col1>\r\n\t\t</Layout>\r\n\t);\r\n}\r\n"],"mappings":";AAsBI,SAKC,KALD;AApBJ,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,QAAQ,SAAS,YAAY;AACtC,SAAS,oBAAoB;AAC7B,SAAS,aAAa;AAEf,SAAS,iBAAiB;AAChC,QAAM,aAAa,QAAQ,MAAM;AAChC,UAAM,MAAM,oBAAI,IAAoB;AACpC,UAAM;AAAA,MAAQ,CAAC,MACd,EAAE,WAAW,QAAQ,CAAC,MAAM,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC;AAAA,IAC9D;AACA,WAAO,MAAM,KAAK,IAAI,QAAQ,CAAC,EAC7B,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EACvC,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO,EAAE,MAAM,MAAM,EAAE;AAAA,EAC3C,GAAG,CAAC,CAAC;AAEL,SACC,oBAAC,UAAO,MAAK,OAAM,WAAU,YAC5B,8BAAC,OAAO,MAAP,EAAY,SAAO,MAAC,WAAU,gBAC9B;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACA,SAAS,EAAE,SAAS,GAAG,GAAG,GAAG;AAAA,MAC7B,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,MAC5B,YAAY,EAAE,UAAU,IAAI;AAAA,MAE5B;AAAA,4BAAC,WAAQ,SAAQ,MAAK,WAAU,WAAU,wBAE1C;AAAA,QACA,oBAAC,QAAK,YAAW,oBAAmB,WAAU,YAAW,oCAEzD;AAAA,QACA,oBAAC,SAAI,WAAU,sCACb,qBAAW,IAAI,CAAC,QAChB,oBAAC,gBAA4B,MAAM,IAAI,MAAM,OAAO,IAAI,SAArC,IAAI,IAAwC,CAC/D,GACF;AAAA;AAAA;AAAA,EACD,GACD,GACD;AAEF;","names":[]}
@@ -30,7 +30,7 @@ function CategoryPage({ category: propCategory }) {
30
30
  return /* @__PURE__ */ jsx(Layout, { type: "col", className: "sg:py-16", as: "main", children: /* @__PURE__ */ jsx(Layout.Col1, { hideDiv: true, children: /* @__PURE__ */ jsxs(
31
31
  motion.div,
32
32
  {
33
- initial: { opacity: 0, y: 20 },
33
+ initial: { opacity: 1, y: 20 },
34
34
  animate: { opacity: 1, y: 0 },
35
35
  transition: { duration: 0.3 },
36
36
  children: [
@@ -49,7 +49,9 @@ function CategoryPage({ category: propCategory }) {
49
49
  /* @__PURE__ */ jsxs(Text, { foreground: "muted-foreground", className: "sg:mb-12", children: [
50
50
  "Explore ",
51
51
  categoryPosts.length,
52
- " story in this category."
52
+ " ",
53
+ categoryPosts.length === 1 ? "story" : "stories",
54
+ " in this category."
53
55
  ] }),
54
56
  /* @__PURE__ */ jsx(
55
57
  PostListWithFilters,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/pages/category/category-page.tsx"],"sourcesContent":["\"use client\";\nimport { useMemo } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { Layout, Heading, Text } from \"../../primitives/index\";\nimport { LinkButton } from \"../../primitives/buttons/link-button\";\nimport { posts } from \"../../../data/posts\";\nimport { PostListWithFilters } from \"../../blocks/post-list/post-list-with-filters\";\nimport EmptyState from \"../../blocks/empty-state/EmptyState\";\n\ninterface Props {\n category?: string;\n}\n\nexport function CategoryPage({ category: propCategory }: Props) {\n // In a real app, this would come from useParams()\n // For the library/storybook, we allow passing it as a prop\n const category = propCategory || \"Design\";\n const decodedCategory = decodeURIComponent(category);\n\n const categoryPosts = useMemo(\n () => posts.filter((p) => p.categories.includes(decodedCategory)),\n [decodedCategory],\n );\n\n if (!decodedCategory || categoryPosts.length === 0) {\n return (\n <Layout type=\"col\" className=\"sg:py-16\">\n <Layout.Col1 hideDiv>\n <EmptyState\n icon=\"Tag\"\n title=\"Category not found\"\n description={\n decodedCategory\n ? `No posts found in \"${decodedCategory}\". It may have been removed or renamed.`\n : \"This category doesn't exist.\"\n }\n actionLabel=\"Browse all posts\"\n actionTo=\"/posts\"\n className=\"sg:min-h-[60vh]\"\n />\n </Layout.Col1>\n </Layout>\n );\n }\n\n return (\n <Layout type=\"col\" className=\"sg:py-16\" as=\"main\">\n <Layout.Col1 hideDiv>\n <motion.div\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.3 }}\n >\n <LinkButton\n variant=\"ghost\"\n size=\"sm\"\n to=\"/posts\"\n iconStart=\"ArrowLeft\"\n className=\"sg:mb-6\"\n >\n All Posts\n </LinkButton>\n\n <Heading variant=\"h1\" className=\"sg:mb-2 sg:capitalize\">\n {decodedCategory}\n </Heading>\n <Text foreground=\"muted-foreground\" className=\"sg:mb-12\">\n Explore {categoryPosts.length} story in this category.\n </Text>\n\n <PostListWithFilters\n posts={categoryPosts}\n hideSearch\n filterMode=\"drawer\"\n />\n </motion.div>\n </Layout.Col1>\n </Layout>\n );\n}\n"],"mappings":";AA4BU,cAsCA,YAtCA;AA3BV,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,QAAQ,SAAS,YAAY;AACtC,SAAS,kBAAkB;AAC3B,SAAS,aAAa;AACtB,SAAS,2BAA2B;AACpC,OAAO,gBAAgB;AAMhB,SAAS,aAAa,EAAE,UAAU,aAAa,GAAU;AAG9D,QAAM,WAAW,gBAAgB;AACjC,QAAM,kBAAkB,mBAAmB,QAAQ;AAEnD,QAAM,gBAAgB;AAAA,IACpB,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,eAAe,CAAC;AAAA,IAChE,CAAC,eAAe;AAAA,EAClB;AAEA,MAAI,CAAC,mBAAmB,cAAc,WAAW,GAAG;AAClD,WACE,oBAAC,UAAO,MAAK,OAAM,WAAU,YAC3B,8BAAC,OAAO,MAAP,EAAY,SAAO,MAClB;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,aACE,kBACI,sBAAsB,eAAe,4CACrC;AAAA,QAEN,aAAY;AAAA,QACZ,UAAS;AAAA,QACT,WAAU;AAAA;AAAA,IACZ,GACF,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,UAAO,MAAK,OAAM,WAAU,YAAW,IAAG,QACzC,8BAAC,OAAO,MAAP,EAAY,SAAO,MAClB;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACC,SAAS,EAAE,SAAS,GAAG,GAAG,GAAG;AAAA,MAC7B,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,MAC5B,YAAY,EAAE,UAAU,IAAI;AAAA,MAE5B;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,IAAG;AAAA,YACH,WAAU;AAAA,YACV,WAAU;AAAA,YACX;AAAA;AAAA,QAED;AAAA,QAEA,oBAAC,WAAQ,SAAQ,MAAK,WAAU,yBAC7B,2BACH;AAAA,QACA,qBAAC,QAAK,YAAW,oBAAmB,WAAU,YAAW;AAAA;AAAA,UAC9C,cAAc;AAAA,UAAO;AAAA,WAChC;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,YACP,YAAU;AAAA,YACV,YAAW;AAAA;AAAA,QACb;AAAA;AAAA;AAAA,EACF,GACF,GACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/pages/category/category-page.tsx"],"sourcesContent":["\"use client\";\nimport { useMemo } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { Layout, Heading, Text } from \"../../primitives/index\";\nimport { LinkButton } from \"../../primitives/buttons/link-button\";\nimport { posts } from \"../../../data/posts\";\nimport { PostListWithFilters } from \"../../blocks/post-list/post-list-with-filters\";\nimport EmptyState from \"../../blocks/empty-state/EmptyState\";\n\ninterface Props {\n category?: string;\n}\n\nexport function CategoryPage({ category: propCategory }: Props) {\n // In a real app, this would come from useParams()\n // For the library/storybook, we allow passing it as a prop\n const category = propCategory || \"Design\";\n const decodedCategory = decodeURIComponent(category);\n\n const categoryPosts = useMemo(\n () => posts.filter((p) => p.categories.includes(decodedCategory)),\n [decodedCategory],\n );\n\n if (!decodedCategory || categoryPosts.length === 0) {\n return (\n <Layout type=\"col\" className=\"sg:py-16\">\n <Layout.Col1 hideDiv>\n <EmptyState\n icon=\"Tag\"\n title=\"Category not found\"\n description={\n decodedCategory\n ? `No posts found in \"${decodedCategory}\". It may have been removed or renamed.`\n : \"This category doesn't exist.\"\n }\n actionLabel=\"Browse all posts\"\n actionTo=\"/posts\"\n className=\"sg:min-h-[60vh]\"\n />\n </Layout.Col1>\n </Layout>\n );\n }\n\n return (\n <Layout type=\"col\" className=\"sg:py-16\" as=\"main\">\n <Layout.Col1 hideDiv>\n <motion.div\n initial={{ opacity: 1, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.3 }}\n >\n <LinkButton\n variant=\"ghost\"\n size=\"sm\"\n to=\"/posts\"\n iconStart=\"ArrowLeft\"\n className=\"sg:mb-6\"\n >\n All Posts\n </LinkButton>\n\n <Heading variant=\"h1\" className=\"sg:mb-2 sg:capitalize\">\n {decodedCategory}\n </Heading>\n <Text foreground=\"muted-foreground\" className=\"sg:mb-12\">\n Explore {categoryPosts.length}{\" \"}\n {categoryPosts.length === 1 ? \"story\" : \"stories\"} in this category.\n </Text>\n\n <PostListWithFilters\n posts={categoryPosts}\n hideSearch\n filterMode=\"drawer\"\n />\n </motion.div>\n </Layout.Col1>\n </Layout>\n );\n}\n"],"mappings":";AA4BU,cAsCA,YAtCA;AA3BV,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,QAAQ,SAAS,YAAY;AACtC,SAAS,kBAAkB;AAC3B,SAAS,aAAa;AACtB,SAAS,2BAA2B;AACpC,OAAO,gBAAgB;AAMhB,SAAS,aAAa,EAAE,UAAU,aAAa,GAAU;AAG9D,QAAM,WAAW,gBAAgB;AACjC,QAAM,kBAAkB,mBAAmB,QAAQ;AAEnD,QAAM,gBAAgB;AAAA,IACpB,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,eAAe,CAAC;AAAA,IAChE,CAAC,eAAe;AAAA,EAClB;AAEA,MAAI,CAAC,mBAAmB,cAAc,WAAW,GAAG;AAClD,WACE,oBAAC,UAAO,MAAK,OAAM,WAAU,YAC3B,8BAAC,OAAO,MAAP,EAAY,SAAO,MAClB;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAM;AAAA,QACN,aACE,kBACI,sBAAsB,eAAe,4CACrC;AAAA,QAEN,aAAY;AAAA,QACZ,UAAS;AAAA,QACT,WAAU;AAAA;AAAA,IACZ,GACF,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,UAAO,MAAK,OAAM,WAAU,YAAW,IAAG,QACzC,8BAAC,OAAO,MAAP,EAAY,SAAO,MAClB;AAAA,IAAC,OAAO;AAAA,IAAP;AAAA,MACC,SAAS,EAAE,SAAS,GAAG,GAAG,GAAG;AAAA,MAC7B,SAAS,EAAE,SAAS,GAAG,GAAG,EAAE;AAAA,MAC5B,YAAY,EAAE,UAAU,IAAI;AAAA,MAE5B;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,IAAG;AAAA,YACH,WAAU;AAAA,YACV,WAAU;AAAA,YACX;AAAA;AAAA,QAED;AAAA,QAEA,oBAAC,WAAQ,SAAQ,MAAK,WAAU,yBAC7B,2BACH;AAAA,QACA,qBAAC,QAAK,YAAW,oBAAmB,WAAU,YAAW;AAAA;AAAA,UAC9C,cAAc;AAAA,UAAQ;AAAA,UAC9B,cAAc,WAAW,IAAI,UAAU;AAAA,UAAU;AAAA,WACpD;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,YACP,YAAU;AAAA,YACV,YAAW;AAAA;AAAA,QACb;AAAA;AAAA;AAAA,EACF,GACF,GACF;AAEJ;","names":[]}
@@ -74,9 +74,9 @@ function ChatPage() {
74
74
  return /* @__PURE__ */ jsx(Layout, { type: "container", bgColor: "background", className: "sg:min-h-screen", children: /* @__PURE__ */ jsx(Layout.Col1, { className: "sg:flex sg:flex-col sg:items-center", children: /* @__PURE__ */ jsxs(
75
75
  motion.div,
76
76
  {
77
- initial: { opacity: 0, y: 20 },
77
+ initial: { opacity: 1, y: 20 },
78
78
  animate: { opacity: 1, y: 0 },
79
- exit: { opacity: 0, y: -20 },
79
+ exit: { opacity: 1, y: -20 },
80
80
  transition: { duration: 0.3 },
81
81
  className: "sg:container sg:py-8 sg:flex sg:flex-col sg:max-w-2xl sg:mx-auto sg:w-full",
82
82
  style: { height: "calc(100vh - 2rem)" },
@@ -97,7 +97,7 @@ function ChatPage() {
97
97
  messages.map((msg) => /* @__PURE__ */ jsxs(
98
98
  motion.div,
99
99
  {
100
- initial: { opacity: 0, y: 10 },
100
+ initial: { opacity: 1, y: 10 },
101
101
  animate: { opacity: 1, y: 0 },
102
102
  transition: { duration: 0.2 },
103
103
  className: cn(
@@ -147,7 +147,7 @@ function ChatPage() {
147
147
  isTyping && /* @__PURE__ */ jsxs(
148
148
  motion.div,
149
149
  {
150
- initial: { opacity: 0, y: 10 },
150
+ initial: { opacity: 1, y: 10 },
151
151
  animate: { opacity: 1, y: 0 },
152
152
  className: "sg:flex sg:gap-3",
153
153
  children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/pages/chat/chat-page.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useRef, useEffect } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { Send, Bot, User, Sparkles } from \"lucide-react\";\nimport { Layout, Button, Input, Heading, Text } from \"../../primitives/index\";\nimport { cn } from \"../../../utils/index\";\n\ninterface Message {\n id: string;\n role: \"user\" | \"assistant\";\n content: string;\n timestamp: Date;\n}\n\nconst mockResponses: Record<string, string> = {\n hello:\n \"Hey there! πŸ‘‹ I'm **Storied Bot**, your friendly AI assistant. I can help you explore blog posts, find resources, or just chat. What's on your mind?\",\n help: \"Sure! Here's what I can help with:\\n\\n- πŸ” **Search posts** β€” Ask me about any topic\\n- πŸ“š **Recommend reads** β€” I'll suggest posts based on your interests\\n- 🎨 **Design tips** β€” Questions about typography, color, layout\\n- πŸ’‘ **General chat** β€” I'm happy to talk about anything!\",\n posts:\n \"We have some great posts! Here are a few highlights:\\n\\n1. **The Art of Minimalism** β€” A deep dive into less-is-more design\\n2. **Color Theory Fundamentals** β€” Understanding how colors work together\\n3. **Typography Best Practices** β€” Making your text shine\\n\\nWant me to go deeper on any of these?\",\n design:\n \"Great question about design! Here are some key principles I follow:\\n\\n- **Hierarchy** β€” Guide the eye with size, weight, and contrast\\n- **Whitespace** β€” Give elements room to breathe\\n- **Consistency** β€” Stick to your design system\\n- **Accessibility** β€” Make sure everyone can use your work\\n\\nWould you like to explore our **Design System** page for live examples?\",\n};\n\nconst defaultResponse =\n \"That's an interesting thought! πŸ€”\\n\\nI'm currently running in demo mode, so my responses are limited. In a full implementation, I'd be powered by an AI model and could have much richer conversations.\\n\\nTry asking me about **posts**, **design**, or type **help** to see what I can do!\";\n\nfunction getResponse(input: string): string {\n const lower = input.toLowerCase().trim();\n for (const [key, response] of Object.entries(mockResponses)) {\n if (lower.includes(key)) return response;\n }\n return defaultResponse;\n}\n\nconst initialMessages: Message[] = [\n {\n id: \"welcome\",\n role: \"assistant\",\n content:\n \"Welcome to **Storied Bot**! πŸ€–βœ¨\\n\\nI'm your AI assistant β€” here to help you explore content, answer questions, and chat. This is a demo showcasing how a conversational interface can look and feel.\\n\\nTry saying **hello**, ask about **posts**, or type **help** to get started!\",\n timestamp: new Date(Date.now() - 60000),\n },\n];\n\nexport function ChatPage() {\n const [messages, setMessages] = useState<Message[]>(initialMessages);\n const [input, setInput] = useState(\"\");\n const [isTyping, setIsTyping] = useState(false);\n const scrollRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n scrollRef.current?.scrollTo({\n top: scrollRef.current.scrollHeight,\n behavior: \"smooth\",\n });\n }, [messages, isTyping]);\n\n const sendMessage = () => {\n const text = input.trim();\n if (!text || isTyping) return;\n\n const userMsg: Message = {\n id: crypto.randomUUID(),\n role: \"user\",\n content: text,\n timestamp: new Date(),\n };\n\n setMessages((prev) => [...prev, userMsg]);\n setInput(\"\");\n setIsTyping(true);\n\n // Simulate typing delay\n setTimeout(\n () => {\n const botMsg: Message = {\n id: crypto.randomUUID(),\n role: \"assistant\",\n content: getResponse(text),\n timestamp: new Date(),\n };\n setMessages((prev) => [...prev, botMsg]);\n setIsTyping(false);\n },\n 800 + Math.random() * 1200,\n );\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n sendMessage();\n }\n };\n\n return (\n <Layout type=\"container\" bgColor=\"background\" className=\"sg:min-h-screen\">\n <Layout.Col1 className=\"sg:flex sg:flex-col sg:items-center\">\n <motion.div\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -20 }}\n transition={{ duration: 0.3 }}\n className=\"sg:container sg:py-8 sg:flex sg:flex-col sg:max-w-2xl sg:mx-auto sg:w-full\"\n style={{ height: \"calc(100vh - 2rem)\" }}\n >\n {/* Header */}\n <div className=\"sg:flex sg:items-center sg:gap-3 sg:mb-4 sg:pb-4 sg:border-b sg:border-border\">\n <div className=\"sg:h-10 sg:w-10 sg:rounded-full sg:bg-primary/10 sg:flex sg:items-center sg:justify-center\">\n <Sparkles className=\"sg:h-5 sg:w-5 sg:text-primary\" />\n </div>\n <div>\n <Heading variant=\"h4\" className=\"sg:font-bold sg:leading-tight\">\n Storied Bot\n </Heading>\n <Text className=\"sg:text-xs sg:text-muted-foreground\">\n AI assistant Β· Demo mode\n </Text>\n </div>\n </div>\n\n {/* Messages area */}\n <div\n ref={scrollRef}\n className=\"chat-scroll sg:flex-1 sg:overflow-y-auto sg:space-y-4 sg:pr-2 sg:min-h-0\"\n >\n {messages.map((msg) => (\n <motion.div\n key={msg.id}\n initial={{ opacity: 0, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.2 }}\n className={cn(\n \"sg:flex sg:gap-3 sg:max-w-[85%]\",\n msg.role === \"user\" ? \"sg:ml-auto sg:flex-row-reverse\" : \"\",\n )}\n >\n {/* Avatar */}\n <div\n className={cn(\n \"sg:h-8 sg:w-8 sg:rounded-full sg:flex sg:items-center sg:justify-center sg:shrink-0 sg:mt-0.5\",\n msg.role === \"assistant\"\n ? \"sg:bg-primary/10 sg:text-primary\"\n : \"sg:bg-secondary sg:text-secondary-foreground\",\n )}\n >\n {msg.role === \"assistant\" ? (\n <Bot className=\"sg:h-4 sg:w-4\" />\n ) : (\n <User className=\"sg:h-4 sg:w-4\" />\n )}\n </div>\n\n {/* Bubble */}\n <div\n className={cn(\n \"sg:rounded-2xl sg:px-4 sg:py-2.5 sg:text-sm sg:leading-relaxed\",\n msg.role === \"assistant\"\n ? \"sg:bg-muted sg:text-foreground sg:rounded-tl-sm\"\n : \"sg:bg-primary sg:text-primary-foreground sg:rounded-tr-sm\",\n )}\n >\n <MessageContent content={msg.content} />\n <p\n className={cn(\n \"sg:text-[10px] sg:mt-1.5\",\n msg.role === \"assistant\"\n ? \"sg:text-muted-foreground\"\n : \"sg:text-primary-foreground/60\",\n )}\n >\n {msg.timestamp.toLocaleTimeString([], {\n hour: \"2-digit\",\n minute: \"2-digit\",\n })}\n </p>\n </div>\n </motion.div>\n ))}\n\n {/* Typing indicator */}\n {isTyping && (\n <motion.div\n initial={{ opacity: 0, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n className=\"sg:flex sg:gap-3\"\n >\n <div className=\"sg:h-8 sg:w-8 sg:rounded-full sg:bg-primary/10 sg:text-primary sg:flex sg:items-center sg:justify-center sg:shrink-0\">\n <Bot className=\"sg:h-4 sg:w-4\" />\n </div>\n <div className=\"sg:bg-muted sg:rounded-2xl sg:rounded-tl-sm sg:px-4 sg:py-3 sg:flex sg:items-center sg:gap-1\">\n <span className=\"sg:w-2 sg:h-2 sg:rounded-full sg:bg-muted-foreground/40 sg:animate-bounce [animation-delay:0ms]\" />\n <span className=\"sg:w-2 sg:h-2 sg:rounded-full sg:bg-muted-foreground/40 sg:animate-bounce [animation-delay:150ms]\" />\n <span className=\"sg:w-2 sg:h-2 sg:rounded-full sg:bg-muted-foreground/40 sg:animate-bounce [animation-delay:300ms]\" />\n </div>\n </motion.div>\n )}\n </div>\n\n {/* Input area */}\n <div className=\"sg:pt-4 sg:mt-4 sg:border-t sg:border-border\">\n <div className=\"sg:flex sg:gap-2\">\n <Input\n ref={inputRef}\n placeholder=\"Type a message…\"\n value={input}\n onChange={(e) => setInput(e.target.value)}\n onKeyDown={handleKeyDown}\n disabled={isTyping}\n className=\"sg:flex-1\"\n />\n <Button\n onClick={sendMessage}\n disabled={!input.trim() || isTyping}\n aria-label=\"Send message\"\n >\n <Send className=\"sg:h-4 sg:w-4\" />\n </Button>\n </div>\n <Text className=\"sg:text-[11px] sg:text-muted-foreground sg:text-center sg:mt-2\">\n Demo mode β€” responses are pre-written. Try: hello, help, posts,\n design\n </Text>\n </div>\n </motion.div>\n </Layout.Col1>\n </Layout>\n );\n}\n\n/** Simple markdown-ish renderer for bold and line breaks */\nfunction MessageContent({ content }: { content: string }) {\n const lines = content.split(\"\\n\");\n return (\n <div className=\"sg:space-y-1.5\">\n {lines.map((line, i) => {\n if (line.trim() === \"\") return <br key={i} />;\n // Bold: **text**\n const parts = line.split(/(\\*\\*[^*]+\\*\\*)/g);\n return (\n <p key={i}>\n {parts.map((part, j) =>\n part.startsWith(\"**\") && part.endsWith(\"**\") ? (\n <strong key={j} className=\"sg:font-semibold\">\n {part.slice(2, -2)}\n </strong>\n ) : (\n <span key={j}>{part}</span>\n ),\n )}\n </p>\n );\n })}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/pages/chat/chat-page.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useRef, useEffect } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { Send, Bot, User, Sparkles } from \"lucide-react\";\nimport { Layout, Button, Input, Heading, Text } from \"../../primitives/index\";\nimport { cn } from \"../../../utils/index\";\n\ninterface Message {\n id: string;\n role: \"user\" | \"assistant\";\n content: string;\n timestamp: Date;\n}\n\nconst mockResponses: Record<string, string> = {\n hello:\n \"Hey there! πŸ‘‹ I'm **Storied Bot**, your friendly AI assistant. I can help you explore blog posts, find resources, or just chat. What's on your mind?\",\n help: \"Sure! Here's what I can help with:\\n\\n- πŸ” **Search posts** β€” Ask me about any topic\\n- πŸ“š **Recommend reads** β€” I'll suggest posts based on your interests\\n- 🎨 **Design tips** β€” Questions about typography, color, layout\\n- πŸ’‘ **General chat** β€” I'm happy to talk about anything!\",\n posts:\n \"We have some great posts! Here are a few highlights:\\n\\n1. **The Art of Minimalism** β€” A deep dive into less-is-more design\\n2. **Color Theory Fundamentals** β€” Understanding how colors work together\\n3. **Typography Best Practices** β€” Making your text shine\\n\\nWant me to go deeper on any of these?\",\n design:\n \"Great question about design! Here are some key principles I follow:\\n\\n- **Hierarchy** β€” Guide the eye with size, weight, and contrast\\n- **Whitespace** β€” Give elements room to breathe\\n- **Consistency** β€” Stick to your design system\\n- **Accessibility** β€” Make sure everyone can use your work\\n\\nWould you like to explore our **Design System** page for live examples?\",\n};\n\nconst defaultResponse =\n \"That's an interesting thought! πŸ€”\\n\\nI'm currently running in demo mode, so my responses are limited. In a full implementation, I'd be powered by an AI model and could have much richer conversations.\\n\\nTry asking me about **posts**, **design**, or type **help** to see what I can do!\";\n\nfunction getResponse(input: string): string {\n const lower = input.toLowerCase().trim();\n for (const [key, response] of Object.entries(mockResponses)) {\n if (lower.includes(key)) return response;\n }\n return defaultResponse;\n}\n\nconst initialMessages: Message[] = [\n {\n id: \"welcome\",\n role: \"assistant\",\n content:\n \"Welcome to **Storied Bot**! πŸ€–βœ¨\\n\\nI'm your AI assistant β€” here to help you explore content, answer questions, and chat. This is a demo showcasing how a conversational interface can look and feel.\\n\\nTry saying **hello**, ask about **posts**, or type **help** to get started!\",\n timestamp: new Date(Date.now() - 60000),\n },\n];\n\nexport function ChatPage() {\n const [messages, setMessages] = useState<Message[]>(initialMessages);\n const [input, setInput] = useState(\"\");\n const [isTyping, setIsTyping] = useState(false);\n const scrollRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n scrollRef.current?.scrollTo({\n top: scrollRef.current.scrollHeight,\n behavior: \"smooth\",\n });\n }, [messages, isTyping]);\n\n const sendMessage = () => {\n const text = input.trim();\n if (!text || isTyping) return;\n\n const userMsg: Message = {\n id: crypto.randomUUID(),\n role: \"user\",\n content: text,\n timestamp: new Date(),\n };\n\n setMessages((prev) => [...prev, userMsg]);\n setInput(\"\");\n setIsTyping(true);\n\n // Simulate typing delay\n setTimeout(\n () => {\n const botMsg: Message = {\n id: crypto.randomUUID(),\n role: \"assistant\",\n content: getResponse(text),\n timestamp: new Date(),\n };\n setMessages((prev) => [...prev, botMsg]);\n setIsTyping(false);\n },\n 800 + Math.random() * 1200,\n );\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n sendMessage();\n }\n };\n\n return (\n <Layout type=\"container\" bgColor=\"background\" className=\"sg:min-h-screen\">\n <Layout.Col1 className=\"sg:flex sg:flex-col sg:items-center\">\n <motion.div\n initial={{ opacity: 1, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 1, y: -20 }}\n transition={{ duration: 0.3 }}\n className=\"sg:container sg:py-8 sg:flex sg:flex-col sg:max-w-2xl sg:mx-auto sg:w-full\"\n style={{ height: \"calc(100vh - 2rem)\" }}\n >\n {/* Header */}\n <div className=\"sg:flex sg:items-center sg:gap-3 sg:mb-4 sg:pb-4 sg:border-b sg:border-border\">\n <div className=\"sg:h-10 sg:w-10 sg:rounded-full sg:bg-primary/10 sg:flex sg:items-center sg:justify-center\">\n <Sparkles className=\"sg:h-5 sg:w-5 sg:text-primary\" />\n </div>\n <div>\n <Heading variant=\"h4\" className=\"sg:font-bold sg:leading-tight\">\n Storied Bot\n </Heading>\n <Text className=\"sg:text-xs sg:text-muted-foreground\">\n AI assistant Β· Demo mode\n </Text>\n </div>\n </div>\n\n {/* Messages area */}\n <div\n ref={scrollRef}\n className=\"chat-scroll sg:flex-1 sg:overflow-y-auto sg:space-y-4 sg:pr-2 sg:min-h-0\"\n >\n {messages.map((msg) => (\n <motion.div\n key={msg.id}\n initial={{ opacity: 1, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.2 }}\n className={cn(\n \"sg:flex sg:gap-3 sg:max-w-[85%]\",\n msg.role === \"user\" ? \"sg:ml-auto sg:flex-row-reverse\" : \"\",\n )}\n >\n {/* Avatar */}\n <div\n className={cn(\n \"sg:h-8 sg:w-8 sg:rounded-full sg:flex sg:items-center sg:justify-center sg:shrink-0 sg:mt-0.5\",\n msg.role === \"assistant\"\n ? \"sg:bg-primary/10 sg:text-primary\"\n : \"sg:bg-secondary sg:text-secondary-foreground\",\n )}\n >\n {msg.role === \"assistant\" ? (\n <Bot className=\"sg:h-4 sg:w-4\" />\n ) : (\n <User className=\"sg:h-4 sg:w-4\" />\n )}\n </div>\n\n {/* Bubble */}\n <div\n className={cn(\n \"sg:rounded-2xl sg:px-4 sg:py-2.5 sg:text-sm sg:leading-relaxed\",\n msg.role === \"assistant\"\n ? \"sg:bg-muted sg:text-foreground sg:rounded-tl-sm\"\n : \"sg:bg-primary sg:text-primary-foreground sg:rounded-tr-sm\",\n )}\n >\n <MessageContent content={msg.content} />\n <p\n className={cn(\n \"sg:text-[10px] sg:mt-1.5\",\n msg.role === \"assistant\"\n ? \"sg:text-muted-foreground\"\n : \"sg:text-primary-foreground/60\",\n )}\n >\n {msg.timestamp.toLocaleTimeString([], {\n hour: \"2-digit\",\n minute: \"2-digit\",\n })}\n </p>\n </div>\n </motion.div>\n ))}\n\n {/* Typing indicator */}\n {isTyping && (\n <motion.div\n initial={{ opacity: 1, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n className=\"sg:flex sg:gap-3\"\n >\n <div className=\"sg:h-8 sg:w-8 sg:rounded-full sg:bg-primary/10 sg:text-primary sg:flex sg:items-center sg:justify-center sg:shrink-0\">\n <Bot className=\"sg:h-4 sg:w-4\" />\n </div>\n <div className=\"sg:bg-muted sg:rounded-2xl sg:rounded-tl-sm sg:px-4 sg:py-3 sg:flex sg:items-center sg:gap-1\">\n <span className=\"sg:w-2 sg:h-2 sg:rounded-full sg:bg-muted-foreground/40 sg:animate-bounce [animation-delay:0ms]\" />\n <span className=\"sg:w-2 sg:h-2 sg:rounded-full sg:bg-muted-foreground/40 sg:animate-bounce [animation-delay:150ms]\" />\n <span className=\"sg:w-2 sg:h-2 sg:rounded-full sg:bg-muted-foreground/40 sg:animate-bounce [animation-delay:300ms]\" />\n </div>\n </motion.div>\n )}\n </div>\n\n {/* Input area */}\n <div className=\"sg:pt-4 sg:mt-4 sg:border-t sg:border-border\">\n <div className=\"sg:flex sg:gap-2\">\n <Input\n ref={inputRef}\n placeholder=\"Type a message…\"\n value={input}\n onChange={(e) => setInput(e.target.value)}\n onKeyDown={handleKeyDown}\n disabled={isTyping}\n className=\"sg:flex-1\"\n />\n <Button\n onClick={sendMessage}\n disabled={!input.trim() || isTyping}\n aria-label=\"Send message\"\n >\n <Send className=\"sg:h-4 sg:w-4\" />\n </Button>\n </div>\n <Text className=\"sg:text-[11px] sg:text-muted-foreground sg:text-center sg:mt-2\">\n Demo mode β€” responses are pre-written. Try: hello, help, posts,\n design\n </Text>\n </div>\n </motion.div>\n </Layout.Col1>\n </Layout>\n );\n}\n\n/** Simple markdown-ish renderer for bold and line breaks */\nfunction MessageContent({ content }: { content: string }) {\n const lines = content.split(\"\\n\");\n return (\n <div className=\"sg:space-y-1.5\">\n {lines.map((line, i) => {\n if (line.trim() === \"\") return <br key={i} />;\n // Bold: **text**\n const parts = line.split(/(\\*\\*[^*]+\\*\\*)/g);\n return (\n <p key={i}>\n {parts.map((part, j) =>\n part.startsWith(\"**\") && part.endsWith(\"**\") ? (\n <strong key={j} className=\"sg:font-semibold\">\n {part.slice(2, -2)}\n </strong>\n ) : (\n <span key={j}>{part}</span>\n ),\n )}\n </p>\n );\n })}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+
3
+ declare function ContactPage(): React.JSX.Element;
4
+
5
+ export { ContactPage };