20260720172840

This commit is contained in:
oneao committed 2026-07-20 17:28:47 +08:00
1 parent cda7f16d3a
commit 94207c77ff
3117 files changed
+201407 -1236

No files matched your search

@@ -0,0 +1,47 @@
# _semantic
## Source
```vue
<script setup lang="ts">
import { computed } from 'vue'
import { SemanticPreview } from '@/components/semantic'
import { useComponentLocale } from '@/composables/use-locale'
import { locales } from '../locales'
const { t } = useComponentLocale(locales)
const semantics = computed(() => [
{ name: 'root', desc: t('root') },
{ name: 'item', desc: t('item') },
])
const heights = [75, 50, 70, 60, 85, 75, 50].map((height, index) => ({
key: `item-${index}`,
data: height,
}))
</script>
<template>
<SemanticPreview
component-name="Masonry"
:semantics="semantics"
>
<template #default="{ classes }">
<a-masonry
:columns="3"
:gutter="16"
:items="heights"
style="width: 100%"
:classes="classes"
>
<template #itemRender="{ data, index }">
<a-card size="small" :style="{ height: data }">
{{ index + 1 }}
</a-card>
</template>
</a-masonry>
</template>
</SemanticPreview>
</template>
```
@@ -0,0 +1,41 @@
# Basic
## Description (en-US)
Basic usage.
## Source
```vue
<script setup lang="ts">
const heights = [150, 50, 90, 70, 110, 150, 130, 80, 50, 90, 100, 150, 60, 50, 80].map((height, index) => {
return {
key: `item-${index}`,
data: height,
}
})
</script>
<template>
<a-masonry :columns="4" :gutter="16" :items="heights">
<template #itemRender="{ index, data }">
<template v-if="index === 4">
<a-card size="small">
<template #cover>
<img
alt="food"
src="https://images.unsplash.com/photo-1491961865842-98f7befd1a60?w=523&auto=format"
>
</template>
<a-card-meta title="I'm Special" description="Let's have a meal" />
</a-card>
</template>
<template v-else>
<a-card size="small" :style="{ height: `${data}px` }">
{{ index + 1 }}
</a-card>
</template>
</template>
</a-masonry>
</template>
```
@@ -0,0 +1,93 @@
# Dynamic
## Description (en-US)
Dynamically add and remove items. The `layoutChange` event provides column information for each item.
## Source
```vue
<script setup lang="ts">
import { CloseOutlined } from '@antdv-next/icons'
import { theme } from 'antdv-next'
import { ref } from 'vue'
const { token } = theme.useToken()
const heights = [150, 50, 90, 70, 110, 150, 130, 80, 50, 90, 100, 150, 70, 50, 80]
interface ItemType {
key: number
column?: number
data: number
}
const items = ref<ItemType[]>(
heights.map((height, index) => ({
key: index,
column: index % 4,
data: height,
})),
)
function removeItem(removeKey: number) {
items.value = items.value.filter(({ key }) => key !== removeKey)
}
function addItem() {
const lastItem = items.value[items.value.length - 1]
items.value = [
...items.value,
{
key: lastItem ? lastItem.key + 1 : 0,
data: Math.floor(Math.random() * 100) + 50,
},
]
}
function handleLayoutChange(sortedItems: ItemType[]) {
items.value = items.value.map((item) => {
const matchItem = sortedItems.find(sortedItem => sortedItem.key === item.key)
return matchItem
? {
...item,
column: matchItem.column,
}
: item
})
}
</script>
<template>
<a-flex vertical :gap="16">
<a-masonry
:columns="4"
:gutter="16"
:items="items"
@layout-change="handleLayoutChange"
>
<template #itemRender="{ data, key }">
<a-card size="small" :style="{ height: `${data}px` }">
{{ Number(key) + 1 }}
<a-button
:style="{
position: 'absolute',
insetBlockStart: `${token.paddingSM}px`,
insetInlineEnd: `${token.paddingSM}px`,
}"
size="small"
@click="() => removeItem(key as any)"
>
<template #icon>
<CloseOutlined />
</template>
</a-button>
</a-card>
</template>
</a-masonry>
<a-button block @click="addItem">
Add Item
</a-button>
</a-flex>
</template>
```
@@ -0,0 +1,77 @@
# Fresh
## Description (en-US)
When `fresh` is enabled, the masonry will continuously monitor size changes of child items and automatically re-layout. Click on cards to change their height.
## Source
```vue
<script setup lang="ts">
import { Card } from 'antdv-next'
import { defineComponent, h, ref } from 'vue'
const RandomHeightCard = defineComponent({
props: {
index: {
type: Number,
required: true,
},
defaultHeight: {
type: Number,
required: true,
},
},
setup(props) {
const height = ref(props.defaultHeight)
const handleClick = () => {
height.value = Math.floor(Math.random() * 100) + 50
}
return () =>
h(
Card,
{
size: 'small',
style: { height: `${height.value}px`, transition: 'height 0.3s' },
onClick: handleClick,
},
() => `${props.index + 1} - Click`,
)
},
})
const heights = [150, 50, 90, 70, 110, 150, 130, 80, 50, 90, 100, 150, 60, 50, 80].map(
(height, index) => {
const item: {
key: string
data: number
column?: number
} = {
key: `item-${index}`,
data: height,
}
if (index === 4) {
item.column = 0
}
return item
},
)
</script>
<template>
<a-masonry
fresh
:columns="4"
:gutter="16"
:items="heights"
>
<template #itemRender="{ data, index }">
<RandomHeightCard :index="index" :default-height="data" />
</template>
</a-masonry>
</template>
```
@@ -0,0 +1,43 @@
# Image
## Description (en-US)
Display image list using masonry layout.
## Source
```vue
<script setup lang="ts">
const imageList = [
'https://images.unsplash.com/photo-1510001618818-4b4e3d86bf0f',
'https://images.unsplash.com/photo-1507513319174-e556268bb244',
'https://images.unsplash.com/photo-1474181487882-5abf3f0ba6c2',
'https://images.unsplash.com/photo-1492778297155-7be4c83960c7',
'https://images.unsplash.com/photo-1508062878650-88b52897f298',
'https://images.unsplash.com/photo-1506158278516-d720e72406fc',
'https://images.unsplash.com/photo-1552203274-e3c7bd771d26',
'https://images.unsplash.com/photo-1528163186890-de9b86b54b51',
'https://images.unsplash.com/photo-1727423304224-6d2fd99b864c',
'https://images.unsplash.com/photo-1675090391405-432434e23595',
'https://images.unsplash.com/photo-1554196967-97a8602084d9',
'https://images.unsplash.com/photo-1491961865842-98f7befd1a60',
'https://images.unsplash.com/photo-1721728613411-d56d2ddda959',
'https://images.unsplash.com/photo-1731901245099-20ac7f85dbaa',
'https://images.unsplash.com/photo-1617694455303-59af55af7e58',
'https://images.unsplash.com/photo-1709198165282-1dab551df890',
]
const items = imageList.map((img, index) => ({
key: `item-${index}`,
data: img,
}))
</script>
<template>
<a-masonry :columns="4" :gutter="16" :items="items">
<template #itemRender="{ data }">
<img :src="`${data}?w=523&auto=format`" alt="sample" style="width: 100%">
</template>
</a-masonry>
</template>
```
@@ -0,0 +1,33 @@
# Responsive
## Description (en-US)
Achieve responsive masonry layout for different screen sizes by setting `columns` and `gutter` properties to responsive configuration objects.
## Source
```vue
<script setup lang="ts">
const heights = [120, 55, 85, 160, 95, 140, 75, 110, 65, 130, 90, 145, 55, 100, 80]
const items = heights.map((height, index) => ({
key: `item-${index}`,
data: height,
index,
}))
</script>
<template>
<a-masonry
:columns="{ xs: 1, sm: 2, md: 3, lg: 4 }"
:gutter="{ xs: 8, sm: 12, md: 16 }"
:items="items"
>
<template #itemRender="item">
<a-card size="small" :style="{ height: `${item.data}px` }">
{{ item.index + 1 }}
</a-card>
</template>
</a-masonry>
</template>
```
@@ -0,0 +1,120 @@
# Custom semantic dom styling
## Description (en-US)
Customize semantic structure styles and class names. Supports both object and function forms.
## Source
```vue
<script setup lang="ts">
import type { MasonryProps } from 'antdv-next'
interface MasonryItemType<T = any> {
key: string
data: T
}
const items = [120, 80, 100, 60, 140, 90, 110, 70].map<MasonryItemType<number>>(
(height, index) => ({
key: `item-${index}`,
data: height,
}),
)
const classes = {
root: 'custom-masonry-root',
item: 'custom-masonry-item',
}
const styles: MasonryProps['styles'] = {
root: {
borderRadius: '12px',
padding: '20px',
height: '260px',
backgroundColor: 'rgba(250,250,250,0.5)',
},
item: {
transform: 'scale(0.98)',
transition: 'transform 0.2s ease',
border: '1px solid #ccc',
},
}
const stylesFn: MasonryProps['styles'] = (info) => {
const { props } = info
return {
root: {
border: `2px solid ${typeof props.columns === 'number' && props.columns > 2 ? '#1890ff' : '#52c41a'}`,
padding: '20px',
height: '280px',
backgroundColor: 'rgba(240,248,255,.6)',
},
item: {
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
border: '1px solid #1890ff',
},
} satisfies MasonryProps['styles']
}
</script>
<template>
<a-flex vertical :gap="24">
<div>
<a-typography-title :level="4">
classNames and styles Object
</a-typography-title>
<a-masonry
:columns="4"
:gutter="16"
:items="items"
:classes="classes"
:styles="styles"
>
<template #itemRender="{ data, index }">
<a-card size="small" :style="{ height: `${data}px` }">
{{ index + 1 }}
</a-card>
</template>
</a-masonry>
</div>
<a-divider />
<div>
<a-typography-title :level="4">
classNames and styles Function
</a-typography-title>
<a-masonry
:columns="3"
:gutter="12"
:items="items.slice(0, 6)"
:classes="classes"
:styles="stylesFn"
>
<template #itemRender="{ data, index }">
<a-card size="small" :style="{ height: `${data}px` }">
{{ index + 1 }}
</a-card>
</template>
</a-masonry>
</div>
</a-flex>
</template>
<style scoped>
:deep(.custom-masonry-root) {
border: 1px solid #d9d9d9;
border-radius: 8px;
padding: 16px;
height: 260px;
background-color: #fafafa;
}
:deep(.custom-masonry-item) {
transform: scale(0.98);
transition: transform 0.2s ease;
border-radius: 12px;
border: 1px solid #ccc;
overflow: hidden;
}
</style>
```
@@ -0,0 +1,71 @@
---
title: Masonry
description: A masonry layout component for displaying content with different heights.
---
## When To Use
- When displaying images or cards with irregular heights.
- When content needs to be evenly distributed in columns.
- When column count needs to be responsive.
## Demos
| Demo | Path |
| --- | --- |
| Basic | demo/basic.md |
| Responsive | demo/responsive.md |
| Image | demo/image.md |
| Dynamic | demo/dynamic.md |
| Custom semantic dom styling | demo/style-class.md |
| Fresh | demo/fresh.md |
## API
Common props ref:[Common props](../../docs/vue/common-props.md)
### Props
| Property | Description | Type | Default | Version | [Global Config](../config-provider/docs.md#component-config) |
| --- | --- | --- | --- | --- | --- |
| columns | Number of columns, can be a fixed value or a responsive configuration | number \| &#123; xs?: number; sm?: number; md?: number; lg?: number; xl?: number; xxl?: number &#125; | 3 | - | × |
| fresh | Whether to continuously monitor the size changes of child items | boolean | false | - | × |
| gutter | Spacing, can be a fixed value, responsive configuration, or a configuration for horizontal and vertical spacing | [Gap](#gap) \| [[Gap](#gap), [Gap](#gap)] | 0 | - | × |
| items | Masonry items | [MasonryItem](#masonryitem)[] | - | - | × |
| itemRender | Custom item rendering function | (item: MasonryItem) =&gt; VueNode | - | - | × |
### Events
| Event | Description | Type | Version |
| --- | --- | --- | --- |
| layoutChange | Callback for column sorting changes | (sortInfo: &#123; key: Key; column: number &#125;[]) =&gt; void | - |
### Slots
| Slot | Description | Type | Version |
| --- | --- | --- | --- |
| itemRender | Custom item rendering slot | (itemInfo: MasonryItem & &#123; index: number &#125;) =&gt; VueNode | - |
## Types
### MasonryItem
| Parameter | Description | Type | Default Value |
| --- | --- | --- | --- |
| key | Unique identifier for the item | string \| number | - |
| height | Height of the item | number | - |
| column | Specifies the column to which the item belongs | number | - |
| data | Custom data storage | T | - |
| children | Custom display content, takes precedence over `itemRender` | VueNode | - |
### Gap
`Gap` represents the spacing between items. It can either be a fixed value or a responsive configuration.
```ts
type Gap = undefined | number | Partial<Record<'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl', number>>
```
## Semantic DOM
| _semantic | demo/_semantic.md |