Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/stacked-chat-images.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bloret-crew/blora-design": minor
---

Add the `blora-image-stack` composite element for touch, horizontal wheel, keyboard, and button navigation through a stacked image collection.
2 changes: 1 addition & 1 deletion docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ const themeBootScript = getThemeBootScript();

## 6. 完整跨框架迁移规范

请按 [`migration/from-any-ui-to-blora-design.md`](./migration/from-any-ui-to-blora-design.md) 执行。文档覆盖 Bootstrap、Tailwind、Ant Design、Element Plus、Naive UI、Vuetify、MUI、PrimeVue、shadcn/ui、React、Vue、Svelte、Angular 和手写 UI,并包含 87 个核心组件示例、21 个 add-on 能力示例、npm-only 规则和最终验收清单。
请按 [`migration/from-any-ui-to-blora-design.md`](./migration/from-any-ui-to-blora-design.md) 执行。文档覆盖 Bootstrap、Tailwind、Ant Design、Element Plus、Naive UI、Vuetify、MUI、PrimeVue、shadcn/ui、React、Vue、Svelte、Angular 和手写 UI,并包含 88 个核心组件示例、21 个 add-on 能力示例、npm-only 规则和最终验收清单。

---

Expand Down
9 changes: 9 additions & 0 deletions docs/migration/from-any-ui-to-blora-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,15 @@ import "@bloret-crew/blora-design/auto";
<blora-image src="/images/project.png" alt="项目预览" preview></blora-image>
```

### Image Stack(image-stack)

```html
<blora-image-stack label="络聊图片堆叠">
<blora-image-stack-item src="/images/photo-1.jpg" alt="第一张照片"></blora-image-stack-item>
<blora-image-stack-item src="/images/photo-2.jpg" alt="第二张照片"></blora-image-stack-item>
</blora-image-stack>
```

### Indicator(indicator)

```html
Expand Down
2 changes: 1 addition & 1 deletion docs/refactor/remaining-work.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

- Stable 版本:`2.0.8`
- npm `latest`:`2.0.8`
- 核心组件:87 个
- 核心组件:88 个
- 官方 add-on:Markdown、Thread、QRCode、Effects、Layout、Theming
- 稳定核心 API:已冻结
- 发布状态:已完成最终验收
Expand Down
7 changes: 7 additions & 0 deletions examples/showcase-v2/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,13 @@ <h3>和 Badge 怎么选</h3>
<blora-deck-card variant="content"><div class="showcase-deck-card"><strong>上线检查</strong><p class="blora-text-muted">发布清单 · 第 4 张</p></div></blora-deck-card>
</blora-deck>
</template>
<template data-component="image-stack" data-label="Image Stack" data-group="数据展示" data-eyebrow="Data display" data-kind="Composite CE" data-description="以堆叠卡片浏览相邻图片,支持拖动、横向滚轮和键盘翻阅。">
<blora-image-stack label="络聊图片堆叠" style="width: 22rem;">
<blora-image-stack-item src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='640' height='640'%3E%3Crect width='640' height='640' fill='%234c566a'/%3E%3C/svg%3E" alt="第一张照片"></blora-image-stack-item>
<blora-image-stack-item src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='640' height='640'%3E%3Crect width='640' height='640' fill='%23bf616a'/%3E%3C/svg%3E" alt="第二张照片"></blora-image-stack-item>
<blora-image-stack-item src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='640' height='640'%3E%3Crect width='640' height='640' fill='%2388c0d0'/%3E%3C/svg%3E" alt="第三张照片"></blora-image-stack-item>
</blora-image-stack>
</template>
<template data-component="indicator" data-label="Indicator" data-group="布局" data-eyebrow="Layout" data-kind="CSS-only" data-description="把徽标、状态点或任意元素钉在宿主的角上。">
<div data-showcase-cases>
<section data-showcase-case="Default"><h3>默认(右上)</h3><span class="blora-indicator"><button class="blora-button" type="button">收件箱</button><span class="blora-indicator__item"><span class="blora-badge" data-shape="pill">12</span></span></span></section>
Expand Down
15 changes: 13 additions & 2 deletions packages/blora-design/component-manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@bloret-crew/blora-design",
"version": "2.0.1",
"generatedAt": "2026-08-29T01:15:38.096Z",
"version": "2.0.8",
"generatedAt": "2026-09-29T13:20:35.152Z",
"components": [
{
"name": "accordion",
Expand Down Expand Up @@ -409,6 +409,17 @@
"cssExport": "./components/image.css",
"contract": "contracts/image.contract.json"
},
{
"name": "image-stack",
"status": "beta",
"kind": "custom-element",
"category": "data-display",
"tagName": "blora-image-stack",
"requiresJavaScript": true,
"formAssociated": false,
"cssExport": "./components/image-stack.css",
"contract": "contracts/image-stack.contract.json"
},
{
"name": "indicator",
"status": "stable",
Expand Down
30 changes: 30 additions & 0 deletions packages/blora-design/contracts/image-stack.contract.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"$schema": "../../schemas/component-contract.schema.json",
"name": "image-stack",
"category": "data-display",
"status": "beta",
"kind": "custom-element",
"tagName": "blora-image-stack",
"since": "2.0.9",
"requiresJavaScript": true,
"formAssociated": false,
"attributes": {
"current": { "type": "number", "default": 0 },
"label": { "type": "string", "default": "图片堆叠" }
},
"properties": { "current": { "type": "number" } },
"methods": {
"next": { "signature": "() => void" },
"prev": { "signature": "() => void" },
"goTo": { "signature": "(index: number) => void" }
},
"events": {
"blora-image-stack-change": { "detail": "{ index: number }", "bubbles": true, "composed": false }
},
"slots": {
"default": "Declarative <blora-image-stack-item src alt href> definitions; consumed into the official chat-compatible image stack tree."
},
"cssProperties": ["--blora-image-stack-layer", "--blora-image-stack-shift"],
"parts": [],
"accessibilityPattern": "group"
}
40 changes: 40 additions & 0 deletions packages/blora-design/custom-elements.json
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,46 @@
}
]
},
{
"kind": "javascript-module",
"path": "dist/index.js",
"declarations": [
{
"kind": "class",
"name": "BloraImageStack",
"tagName": "blora-image-stack",
"customElement": true,
"description": "image-stack custom element",
"superclass": {
"name": "BloraElement",
"module": "dist/index.js"
}
}
],
"exports": [
{
"kind": "js",
"name": "BloraImageStack",
"declaration": {
"name": "BloraImageStack"
}
},
{
"kind": "js",
"name": "defineBloraImageStack",
"declaration": {
"name": "defineBloraImageStack"
}
},
{
"kind": "custom-element-definition",
"name": "blora-image-stack",
"declaration": {
"name": "BloraImageStack"
}
}
]
},
{
"kind": "javascript-module",
"path": "dist/index.js",
Expand Down
1 change: 1 addition & 0 deletions packages/blora-design/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
"./components/copy.css": "./dist/components/copy/copy.css",
"./components/datepicker.css": "./dist/components/datepicker/datepicker.css",
"./components/deck.css": "./dist/components/deck/deck.css",
"./components/image-stack.css": "./dist/components/image-stack/image-stack.css",
"./components/dock.css": "./dist/components/dock/dock.css",
"./components/fab.css": "./dist/components/fab/fab.css",
"./components/masonry.css": "./dist/components/masonry/masonry.css",
Expand Down
3 changes: 3 additions & 0 deletions packages/blora-design/src/auto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { defineBloraTreeSelect } from "./components/tree-select/index.js";
import { defineBloraCalendar } from "./components/calendar/index.js";
import { defineBloraCarousel } from "./components/carousel/index.js";
import { defineBloraDeck } from "./components/deck/index.js";
import { defineBloraImageStack } from "./components/image-stack/index.js";
import { defineBloraImage } from "./components/image/index.js";
import { defineBloraDock } from "./components/dock/index.js";
import { defineBloraMegamenu } from "./components/megamenu/index.js";
Expand Down Expand Up @@ -119,6 +120,7 @@ export function defineAllBloraElements(registry: CustomElementRegistry = customE
defineBloraCalendar(registry);
defineBloraCarousel(registry);
defineBloraDeck(registry);
defineBloraImageStack(registry);
defineBloraImage(registry);
defineBloraDock(registry);
defineBloraMegamenu(registry);
Expand Down Expand Up @@ -187,6 +189,7 @@ export {
defineBloraCalendar,
defineBloraCarousel,
defineBloraDeck,
defineBloraImageStack,
defineBloraImage,
defineBloraDock,
defineBloraMegamenu,
Expand Down
85 changes: 85 additions & 0 deletions packages/blora-design/src/components/image-stack/image-stack.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/* Blora Design 2.0 - chat-compatible stacked photo browsing. */

blora-image-stack { display: block; }
.blora-image-stack {
position: relative;
width: min(300px, calc(64vw - 48px));
height: min(320px, 68vw);
margin: var(--blora-space-8) var(--blora-space-8) 0;
isolation: isolate;
overscroll-behavior-x: contain;
touch-action: pan-y;
cursor: grab;
user-select: none;
outline: none;
}
.blora-image-stack:active { cursor: grabbing; }
.blora-image-stack:focus-visible { box-shadow: 0 0 0 2px var(--blora-color-action-primary-default); border-radius: var(--blora-radius-lg); }
.blora-image-stack__count {
display: flex;
align-items: center;
gap: var(--blora-space-2);
width: min(300px, calc(64vw - 48px));
margin: 0 var(--blora-space-3) var(--blora-space-3);
padding: 0 var(--blora-space-3);
border: 0;
color: var(--blora-color-action-primary-default);
background: transparent;
font: inherit;
font-size: var(--blora-text-sm);
font-weight: 600;
text-align: start;
cursor: pointer;
}
.blora-image-stack__count:focus-visible { outline: 2px solid var(--blora-color-action-primary-default); outline-offset: 3px; border-radius: var(--blora-radius-sm); }
.blora-image-stack__index { margin-inline-start: auto; color: var(--blora-color-text-muted); font-size: var(--blora-text-sm); font-weight: 400; }
.blora-image-stack__tile {
position: absolute;
inset: 0;
overflow: hidden;
border-radius: 22px;
background: var(--blora-color-surface-raised);
box-shadow: var(--blora-shadow-2);
transform: translate(calc(var(--blora-image-stack-layer, 0) * -10px), calc(var(--blora-image-stack-layer, 0) * -10px));
z-index: calc(4 - var(--blora-image-stack-layer, 0));
transition: transform var(--blora-duration-base) var(--blora-easing-standard), opacity var(--blora-duration-base) var(--blora-easing-standard);
}
.blora-image-stack__tile.is-hidden { opacity: 0; pointer-events: none; }
.blora-image-stack__tile[data-passed] { transform: translate(calc(-1 * var(--blora-image-stack-shift, 0px)), var(--blora-space-3)) rotate(-5deg); opacity: 1; z-index: 0; pointer-events: auto; }
.blora-image-stack__tile.is-departing { animation: blora-image-stack-pass var(--blora-duration-base) var(--blora-easing-standard) forwards; }
.blora-image-stack__tile.is-returning { transform: translate(calc(-1 * var(--blora-image-stack-shift, 0px)), var(--blora-space-3)) rotate(-5deg); animation: blora-image-stack-return var(--blora-duration-base) var(--blora-easing-standard) forwards; z-index: 5; }
.blora-image-stack__tile img { display: block; width: 100%; height: 100%; object-fit: cover; }
@keyframes blora-image-stack-pass { to { transform: translate(calc(-1 * var(--blora-image-stack-shift, 0px)), var(--blora-space-3)) rotate(-5deg); opacity: 1; } }
@keyframes blora-image-stack-return { to { transform: translate(0, 0) rotate(0); opacity: 1; } }
.blora-image-stack__nav {
position: absolute;
top: 50%;
z-index: 6;
display: grid;
place-items: center;
width: 38px;
height: 38px;
padding: 0;
border: 0;
border-radius: var(--blora-radius-full);
color: var(--blora-color-text-default);
background: color-mix(in srgb, var(--blora-color-surface-raised) 85%, transparent);
box-shadow: var(--blora-shadow-1);
cursor: pointer;
opacity: 0;
pointer-events: none;
transform: translateY(-50%);
transition: opacity var(--blora-duration-fast) var(--blora-easing-standard);
}
.blora-image-stack__nav--prev { left: var(--blora-space-3); }
.blora-image-stack__nav--next { right: var(--blora-space-3); }
.blora-image-stack:hover .blora-image-stack__nav,
.blora-image-stack:focus-within .blora-image-stack__nav { opacity: 1; pointer-events: auto; }
.blora-image-stack__nav[hidden] { display: none; }
.blora-image-stack__nav:focus-visible { outline: 2px solid var(--blora-color-action-primary-default); outline-offset: 2px; }
.blora-image-gallery[hidden] { display: none; }
.blora-image-gallery { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--blora-space-2); width: calc(100% - var(--blora-space-6)); margin: 0 var(--blora-space-3) var(--blora-space-2); }
.blora-image-gallery__item { display: block; aspect-ratio: 1; overflow: hidden; border-radius: var(--blora-radius-sm); }
.blora-image-gallery__item img { display: block; width: 100%; height: 100%; object-fit: cover; }
@media (hover: none) { .blora-image-stack__nav:not([hidden]) { opacity: 1; pointer-events: auto; } }
@media (prefers-reduced-motion: reduce) { .blora-image-stack__tile, .blora-image-stack__nav { transition: none; } .blora-image-stack__tile.is-departing, .blora-image-stack__tile.is-returning { animation: none; } }
Loading
Loading