diff --git a/docs/.vuepress/notes/zh/theme-guide.ts b/docs/.vuepress/notes/zh/theme-guide.ts index d01f5612..fcdc9e95 100644 --- a/docs/.vuepress/notes/zh/theme-guide.ts +++ b/docs/.vuepress/notes/zh/theme-guide.ts @@ -133,6 +133,7 @@ export const themeGuide = defineNoteConfig({ '链接卡片', '图片卡片', '卡片容器', + '瀑布流容器', '首页布局容器', 'repoCard', 'npmBadge', diff --git a/docs/notes/theme/guide/组件/瀑布流容器.md b/docs/notes/theme/guide/组件/瀑布流容器.md new file mode 100644 index 00000000..fca9f1e8 --- /dev/null +++ b/docs/notes/theme/guide/组件/瀑布流容器.md @@ -0,0 +1,266 @@ +--- +title: 瀑布流容器 +icon: ri:layout-masonry-line +createTime: 2024/12/14 17:17:06 +permalink: /guide/components/card-masonry/ +badge: + text: v1.0.0-rc.121 + +--- + +## 概述 + +瀑布流容器是一个 通用的容器组件,你可以把任何内容放到 `` 里面,容器会自动计算每一个 **项** 的高度, +然后将它们按照瀑布流的方式进行排列。 + +::: details 什么是项 ? + +项 表示的是一个单独的内容,可以是图片、文字、视频等等。 + +- 从 markdown 的语法而言,独占一行的可以被认为是一个项。(该行的前后应该有空行) +- 从 html 的结构而言,容器下的每一个子元素都会被认为是一个项。 + +::: + +```md + + +... + + + + +``` + +## Props + +| 名称 | 类型 | 默认值 | 说明 | +| :--- | :----------------------------------------------- | :----- | :------------- | +| cols | `number \| Record<'sm' \| 'md' \| 'lg', number>` | `3` | 列数 | +| gap | `number` | `16` | 列之间的间距 | + +组件默认会根据屏幕宽度自动调整列数。在空间足够时,默认显示三列,小屏幕下显示双列。 + +你可以通过 `cols` 配置列数。当传入 `number` 时,所有尺寸均显示为 `number` 个卡片。 +传入 `{ sm: number, md: number, lg: number }` 时,根据屏幕宽度自动调整列数。 + +- `sm` : `< 640px` +- `md` : `>= 640px < 960px` +- `lg` : `>= 960px` + +## Markdown 语法支持 + +在 markdown 中,可以使用 `::: card-masonry` 容器代替 ``。 + +``` md +::: card-masonry cols="3" gap="16" + +![](/images/1.png) + + + +::: +``` + +## 示例 + +### 图片瀑布流 + +瀑布流特别适合用于展示图片,你可以直接在将 `![](image_url)` 写到 `::: card-masonry` 中。 + +**输入:** + +``` md +::: card-masonry + +![](image_url) + +![](image_url) + +![](image_url) + +![](image_url) + +![](image_url) + +![](image_url) + +::: +``` + +**输出:** + +::: card-masonry +![a](https://images.unsplash.com/photo-1719937051124-91c677bc58fc?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDF8MHxmZWF0dXJlZC1waG90b3MtZmVlZHwxfHx8ZW58MHx8fHx8) + +![b](https://plus.unsplash.com/premium_photo-1731329153355-1015daf2cb92?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHwyfHx8ZW58MHx8fHx8) + +![c](https://images.unsplash.com/photo-1731323036230-fb37b4d9ed71?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHwzfHx8ZW58MHx8fHx8) + +![a](https://images.unsplash.com/photo-1730630906214-1256b57d65b7?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw0fHx8ZW58MHx8fHx8) + +![b](https://plus.unsplash.com/premium_photo-1733864822156-f3cf26187fd9?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw2fHx8ZW58MHx8fHx8) + +![a](https://images.unsplash.com/photo-1731756748993-85e1513dfc76?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw3fHx8ZW58MHx8fHx8) + +![b](https://images.unsplash.com/photo-1733705879328-a18f2a025c67?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw4fHx8ZW58MHx8fHx) +::: + +### 卡片瀑布流 + +瀑布流也适合用于展示卡片,你可以直接在将 `::: card` 写到 `::: card-masonry` 中。 + +**输入:** + +``` md :collapsed-lines +:::: card-masonry + +::: card title="卡片1" +卡片内容 +::: + +::: card title="卡片2" +卡片内容 + +卡片内容 +::: + +::: card title="卡片3" +卡片内容 +::: + +::: card title="卡片4" +卡片内容 +::: + +::: card title="卡片5" +卡片内容 + +卡片内容 +::: + +::: card title="卡片6" +卡片内容 +::: + +:::: +``` + +**输出:** + +:::: card-masonry + +::: card title="卡片1" +卡片内容 +::: + +::: card title="卡片2" +卡片内容 + +卡片内容 +::: + +::: card title="卡片3" +卡片内容 +::: + +::: card title="卡片4" +卡片内容 +::: + +::: card title="卡片5" +卡片内容 + +卡片内容 +::: + +::: card title="卡片6" +卡片内容 +::: + +:::: + +### 代码块瀑布流 + +**输入:** + +````md :collapsed-lines +:::card-masonry + +```ts +const a = 1 +``` + +```json +{ + "name": "John" +} +``` + +```css +p { + color: red; +} +``` + +```html + + +

Hello world

+ + +``` + +```ts +const a = 12 +const b = 1 +``` + +```rust +fn main() { + println!("Hello, world!"); +} +``` + +::: +```` + +**输出:** + +:::card-masonry + +```ts +const a = 1 +``` + +```json +{ + "name": "John" +} +``` + +```css +p { + color: red; +} +``` + +```html + + +

Hello world

+ + +``` + +```ts +const a = 12 +const b = 1 +``` + +```rust +fn main() { + println!("Hello, world!"); +} +``` + +::: diff --git a/plugins/plugin-md-power/src/node/container/card.ts b/plugins/plugin-md-power/src/node/container/card.ts index bfae0dc8..30df3e09 100644 --- a/plugins/plugin-md-power/src/node/container/card.ts +++ b/plugins/plugin-md-power/src/node/container/card.ts @@ -7,6 +7,11 @@ interface CardAttrs { icon?: string } +interface CardMasonryAttrs { + cols?: number + gap?: number +} + export function cardPlugin(md: Markdown) { /** * ::: card title="xxx" icon="xxx" @@ -36,4 +41,28 @@ export function cardPlugin(md: Markdown) { before: () => '', after: () => '', }) + + /** + * ::: card-masonry cols="2" gap="10" + * ::: card + * xxx + * ::: + * ::: card + * xxx + * ::: + * :::: + */ + createContainerPlugin(md, 'card-masonry', { + before: (info) => { + const { attrs } = resolveAttrs(info) + let cols!: string | number + if (attrs.cols) { + cols = attrs.cols[0] === '{' ? attrs.cols : Number.parseInt(`${attrs.cols}`) + } + const gap = Number.parseInt(`${attrs.gap}`) + + return `= 0 ? ` :gap="${gap}"` : ''}>` + }, + after: () => '', + }) } diff --git a/theme/src/client/components/VPDoc.vue b/theme/src/client/components/VPDoc.vue index a6a3f147..25928108 100644 --- a/theme/src/client/components/VPDoc.vue +++ b/theme/src/client/components/VPDoc.vue @@ -139,7 +139,7 @@ watch( diff --git a/theme/src/client/components/global/VPCard.vue b/theme/src/client/components/global/VPCard.vue index 02a8bc35..1131c2b2 100644 --- a/theme/src/client/components/global/VPCard.vue +++ b/theme/src/client/components/global/VPCard.vue @@ -37,6 +37,7 @@ const icon = computed(() => { display: flex; flex-direction: column; gap: 16px; + width: 100%; padding: 16px 20px; margin: 16px 0; border: solid 1px var(--vp-c-divider); diff --git a/theme/src/client/components/global/VPCardMasonry.vue b/theme/src/client/components/global/VPCardMasonry.vue new file mode 100644 index 00000000..730d86af --- /dev/null +++ b/theme/src/client/components/global/VPCardMasonry.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/theme/src/client/components/global/VPImageCard.vue b/theme/src/client/components/global/VPImageCard.vue index d4937412..b0f6d703 100644 --- a/theme/src/client/components/global/VPImageCard.vue +++ b/theme/src/client/components/global/VPImageCard.vue @@ -114,14 +114,14 @@ const styles = computed(() => { transform: translateY(calc(100% - 60px)); } -:where(.vp-card-grid.cols-3) .image-info { +:where(.vp-card-grid.cols-3, .vp-card-masonry.cols-3) .image-info { padding: 8px 8px 0; font-size: 12px; transform: translateY(calc(100% - 36px)); } @media (max-width: 767px) { - :where(.vp-card-grid.cols-2) .image-info { + :where(.vp-card-grid.cols-2, .vp-card-masonry.cols-2) .image-info { padding: 8px 8px 0; font-size: 12px; transform: translateY(calc(100% - 36px)); @@ -142,7 +142,7 @@ const styles = computed(() => { white-space: nowrap; } -:where(.vp-card-grid.cols-3) .image-info .title { +:where(.vp-card-grid.cols-3, .vp-card-masonry.cols-3) .image-info .title { min-height: 20px; margin: 0 0 8px; font-size: 14px; @@ -150,7 +150,7 @@ const styles = computed(() => { } @media (max-width: 767px) { - :where(.vp-card-grid.cols-2) .image-info .title { + :where(.vp-card-grid.cols-2, .vp-card-masonry.cols-2) .image-info .title { min-height: 20px; margin: 0 0 8px; font-size: 14px; @@ -169,12 +169,12 @@ const styles = computed(() => { color: var(--vp-c-white); } -:where(.vp-card-grid.cols-3) .image-info p { +:where(.vp-card-grid.cols-3, .vp-card-masonry.cols-3) .image-info p { line-height: 20px; } @media (max-width: 767px) { - :where(.vp-card-grid.cols-2) .image-info p { + :where(.vp-card-grid.cols-2, .vp-card-masonry.cols-2) .image-info p { line-height: 20px; } } diff --git a/theme/src/client/components/global/VPLinkCard.vue b/theme/src/client/components/global/VPLinkCard.vue index 5df711c8..0e482330 100644 --- a/theme/src/client/components/global/VPLinkCard.vue +++ b/theme/src/client/components/global/VPLinkCard.vue @@ -33,6 +33,7 @@ defineProps<{ display: flex; gap: 8px; align-items: flex-start; + width: 100%; padding: 16px 20px; margin: 16px 0; background-color: transparent; diff --git a/theme/src/client/globalComponents.ts b/theme/src/client/globalComponents.ts index 189668f6..1c0d95b9 100644 --- a/theme/src/client/globalComponents.ts +++ b/theme/src/client/globalComponents.ts @@ -1,11 +1,13 @@ -import type { App } from 'vue' import VPBadge from '@theme/global/VPBadge.vue' import VPCard from '@theme/global/VPCard.vue' import VPCardGrid from '@theme/global/VPCardGrid.vue' +import VPCardMasonry from '@theme/global/VPCardMasonry.vue' import VPImageCard from '@theme/global/VPImageCard.vue' import VPLinkCard from '@theme/global/VPLinkCard.vue' import VPHomeBox from '@theme/Home/VPHomeBox.vue' import VPIcon from '@theme/VPIcon.vue' +import { hasGlobalComponent } from '@vuepress/helper/client' +import { type App, h, resolveComponent } from 'vue' export function globalComponents(app: App) { app.component('Badge', VPBadge) @@ -23,9 +25,19 @@ export function globalComponents(app: App) { app.component('VPImageCard', VPImageCard) app.component('ImageCard', VPImageCard) + app.component('VPCardMasonry', VPCardMasonry) + app.component('CardMasonry', VPCardMasonry) + app.component('Icon', VPIcon) app.component('VPIcon', VPIcon) app.component('HomeBox', VPHomeBox) app.component('VPHomeBox', VPHomeBox) + + app.component('DocComment', (props) => { + if (hasGlobalComponent('CommentService')) { + return h(resolveComponent('CommentService'), props) + } + return null + }) }