Commit f934277

HPCesia <me@hpcesia.com>
2025-03-14 12:33:21
feat(user components): Link Card
1 parent 7683c9d
Changed files (4)
src/components/user/index.ts
@@ -1,4 +1,5 @@
 export { default as Collapse } from './Collapse.astro';
+export { default as LinkCard } from './LinkCard.astro';
 export { default as Repl } from './Repl.astro';
 export { default as RepoCard } from './RepoCard.astro';
 export { default as Tabs } from './Tabs.astro';
src/components/user/LinkCard.astro
@@ -0,0 +1,23 @@
+---
+import { Icon } from 'astro-icon/components';
+
+interface Props {
+  title: string;
+  description: string;
+  url: string;
+}
+
+let { title, description, url } = Astro.props;
+---
+
+<a href={url} title={title} class="card border-base-content/25 my-4 overflow-hidden border">
+  <div class="card-body flex flex-row items-center justify-between p-4">
+    <div>
+      <div class="card-title">
+        {title}
+      </div>
+      <div class="card-desc text-base-content/50">{description}</div>
+    </div>
+    <Icon name="material-symbols:arrow-right-alt-rounded" class="text-3xl" />
+  </div>
+</a>
src/content/posts/components.mdx
@@ -9,7 +9,7 @@ tags:
 published: 2025-02-10T21:23:23+08:00
 ---
 
-import { Collapse, Repl, RepoCard, TabItem, Tabs, Tooltip } from '@components/user';
+import { Collapse, LinkCard, Repl, RepoCard, TabItem, Tabs, Tooltip } from '@components/user';
 
 Components let you easily reuse a piece of UI or styling consistently. You can use them not just in `.astro` files, but also in `.mdx` files.
 
@@ -203,4 +203,42 @@ Components let you easily reuse a piece of UI or styling consistently. You can u
     <RepoCard repo="withastro/astro" platform='github' />
     ```
   </Fragment>
+</Repl>
+
+### LinkCard
+<Repl>
+  <LinkCard
+    title="Astral Halo"
+    description="A static blog template developed with Astro"
+    url="https://github.com/HPCesia/astral-halo"
+  />
+  <LinkCard
+    title="Astro"
+    description="The all-in-one web framework designed for speed."
+    url="https://astro.build/"
+  />
+  <Fragment slot="desc">
+    <Tabs>
+      <TabItem label="mdx" active>
+        ```jsx
+        <LinkCard
+          title="Astral Halo"
+          description="A static blog template developed with Astro"
+          url="https://github.com/HPCesia/astral-halo"
+        />
+        <LinkCard
+          title="Astro"
+          description="The all-in-one web framework designed for speed."
+          url="https://astro.build/"
+        />
+        ```
+      </TabItem>
+      <TabItem label="md">
+        ```md
+        ::linkcard{title="Astral Halo" description="A static blog template developed with Astro" url="https://github.com/HPCesia/astral-halo"}
+        ::linkcard{title="Astro" description="The all-in-one web framework designed for speed." url="https://astro.build/"}
+        ```
+      </TabItem>
+    </Tabs>
+  </Fragment>
 </Repl>
\ No newline at end of file
src/plugins/rehype-components-list.ts
@@ -1,6 +1,8 @@
 /**
  * All components in this file should sync with the components in `src/components/user`
  */
+import { icons as MaterialSymbols } from '@iconify-json/material-symbols';
+import { getIconData, iconToHTML, iconToSVG } from '@iconify/utils';
 import { h } from 'hastscript';
 import type { Child } from 'hastscript';
 
@@ -26,6 +28,39 @@ const Collapse = function (
   return h('div', { class: wrapperClassName }, [inputNode, titleNode, contentNode]);
 };
 
+const LinkCard = function (props: { title: string; description: string; url: string }) {
+  const { title, description, url } = props;
+  const wrapperClassName = 'card border-base-content/25 my-4 overflow-hidden border';
+  const bodyClassName = 'card-body flex flex-row items-center justify-between p-4';
+  const titleClassName = 'card-title';
+  const descClassName = 'card-desc text-base-content/50';
+
+  const titleNode = h('div', { class: titleClassName }, title);
+  const descNode = h('div', { class: descClassName }, description);
+  const contentNode = h('div', null, [titleNode, descNode]);
+
+  const iconData = getIconData(MaterialSymbols, 'arrow-right-alt-rounded');
+  if (!iconData) {
+    console.error('LinkCard icon not found: material-symbols:arrow-right-alt-rounded');
+    return h('a', { class: wrapperClassName, href: url, title }, 'Link card error');
+  }
+  const { attributes, body } = iconToSVG(iconData);
+  const iconHtml = iconToHTML(body, attributes);
+  const iconNode = h(
+    'span',
+    {
+      class: 'text-3xl',
+    },
+    {
+      type: 'raw',
+      value: iconHtml,
+    }
+  );
+  const bodyNode = h('div', { class: bodyClassName }, [contentNode, iconNode]);
+
+  return h('a', { class: wrapperClassName, href: url, title }, bodyNode);
+};
+
 const Tooltip = function (
   props: {
     tip: string;
@@ -38,4 +73,8 @@ const Tooltip = function (
   return h('div', { class: wrapperClassName, 'data-tip': tip }, children);
 };
 
-export const rehypeComponentsList = { collapse: Collapse, tooltip: Tooltip };
+export const rehypeComponentsList = {
+  collapse: Collapse,
+  linkcard: LinkCard,
+  tooltip: Tooltip,
+};