Skip to content

Commit 42cb3da

Browse files
committed
fix: 解决Issue #956 (增加 exportparts 的例子,且补充到文档里)
1 parent e70ed66 commit 42cb3da

File tree

4 files changed

+301
-9
lines changed

4 files changed

+301
-9
lines changed

package-lock.json

Lines changed: 10 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"dependencies": {
3-
"omi": "^7.6.17"
3+
"omi": "^7.7.13"
44
}
55
}

packages/omi/README.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,110 @@ define('my-app', class extends withTwind(Component) {
245245
})
246246
``` -->
247247

248+
## ExportParts - 样式化嵌套组件
249+
250+
Omi 支持 Web Components 的 `exportparts` 属性,允许将嵌套组件的 CSS parts 暴露给外部进行样式化。这使得在保持样式封装的同时,实现强大的组件组合。
251+
252+
### 基本用法
253+
254+
```tsx
255+
import { render, tag, Component, h } from 'omi'
256+
257+
// 内部组件定义 CSS parts
258+
@tag('inner-button')
259+
class InnerButton extends Component {
260+
static css = `
261+
.btn {
262+
padding: 10px 20px;
263+
border: 2px solid #007bff;
264+
background: #007bff;
265+
color: white;
266+
border-radius: 4px;
267+
cursor: pointer;
268+
}
269+
`
270+
271+
render() {
272+
return (
273+
<button class="btn" part="button">
274+
<span part="icon">🚀</span>
275+
<span part="text">{this.props.children}</span>
276+
</button>
277+
)
278+
}
279+
}
280+
281+
// 容器组件使用 exportparts
282+
@tag('card-component')
283+
class CardComponent extends Component {
284+
static css = `
285+
.card {
286+
border: 1px solid #ddd;
287+
padding: 20px;
288+
border-radius: 8px;
289+
}
290+
291+
/* 通过 ::part() 样式化嵌套组件 */
292+
inner-button::part(button) {
293+
background: #28a745;
294+
border-color: #28a745;
295+
}
296+
`
297+
298+
render() {
299+
return (
300+
<div class="card" part="card">
301+
{/* 导出嵌套组件的 parts */}
302+
<inner-button exportparts="button, icon, text">
303+
Click me
304+
</inner-button>
305+
</div>
306+
)
307+
}
308+
}
309+
310+
// 父组件可以样式化导出的 parts
311+
@tag('app-container')
312+
class AppContainer extends Component {
313+
static css = `
314+
/* 样式化从嵌套组件导出的 parts */
315+
card-component::part(button) {
316+
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
317+
border: none;
318+
border-radius: 25px;
319+
}
320+
321+
card-component::part(icon) {
322+
animation: spin 2s linear infinite;
323+
}
324+
325+
@keyframes spin {
326+
from { transform: rotate(0deg); }
327+
to { transform: rotate(360deg); }
328+
}
329+
`
330+
331+
render() {
332+
return <card-component />
333+
}
334+
}
335+
```
336+
337+
### 核心特性
338+
339+
- **Part 定义**: 使用 `part="part-name"` 属性定义组件中可样式化的部分
340+
- **Part 导出**: 使用 `exportparts="part1, part2"` 暴露嵌套组件的 parts
341+
- **外部样式化**: 使用 `component::part(part-name)` 选择器从外部样式化 parts
342+
- **Part 重命名**: 使用 `exportparts="internal-name:external-name"` 重命名导出的 parts
343+
344+
### 高级示例
345+
346+
完整的工作示例请参考 [`exportparts-example.tsx`](./examples/exportparts-example.tsx),演示了:
347+
- 多层组件嵌套
348+
- Part 重命名和别名
349+
- 复杂样式化场景
350+
- 动画和悬停效果
351+
248352
## Define Cross Framework Component
249353

250354
The case of using Omi component in Vue is as follows:
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import { render, tag, Component, h } from '@/index'
2+
3+
// 内部按钮组件 - 定义了可以被外部样式化的部分
4+
@tag('inner-button')
5+
class InnerButton extends Component {
6+
static css = `
7+
.btn {
8+
padding: 10px 20px;
9+
border: 2px solid #007bff;
10+
background: #007bff;
11+
color: white;
12+
border-radius: 4px;
13+
cursor: pointer;
14+
transition: all 0.3s ease;
15+
font-family: inherit;
16+
font-size: 14px;
17+
}
18+
.btn:hover {
19+
background: #0056b3;
20+
border-color: #0056b3;
21+
}
22+
.icon {
23+
margin-right: 8px;
24+
font-size: 16px;
25+
}
26+
`
27+
28+
render() {
29+
return (
30+
<button class="btn" part="button">
31+
<span class="icon" part="icon">🚀</span>
32+
<span part="text">{this.props.children || 'Click me'}</span>
33+
</button>
34+
)
35+
}
36+
}
37+
38+
// 卡片组件 - 使用 exportparts 将内部组件的 parts 暴露出来
39+
@tag('card-component')
40+
class CardComponent extends Component {
41+
static css = `
42+
.card {
43+
border: 1px solid #ddd;
44+
border-radius: 8px;
45+
padding: 20px;
46+
margin: 10px;
47+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
48+
background: white;
49+
}
50+
.title {
51+
font-size: 18px;
52+
font-weight: bold;
53+
margin-bottom: 10px;
54+
color: #333;
55+
}
56+
`
57+
58+
render() {
59+
return (
60+
<div class="card" part="card">
61+
<h3 class="title" part="title">{this.props.title || 'Card Title'}</h3>
62+
{/* 使用 exportparts 将 inner-button 的 parts 暴露出来 */}
63+
<inner-button exportparts="button, icon, text">
64+
{this.props.buttonText || 'Action'}
65+
</inner-button>
66+
</div>
67+
)
68+
}
69+
}
70+
71+
// 外部容器组件 - 可以通过 ::part() 选择器样式化嵌套组件的部分
72+
@tag('app-container')
73+
class AppContainer extends Component {
74+
static css = `
75+
.container {
76+
max-width: 800px;
77+
margin: 0 auto;
78+
padding: 20px;
79+
font-family: Arial, sans-serif;
80+
}
81+
82+
/* 通过 ::part() 选择器样式化嵌套组件的部分 */
83+
card-component::part(card) {
84+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
85+
color: white;
86+
border: none;
87+
}
88+
89+
card-component::part(title) {
90+
color: #fff;
91+
text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
92+
}
93+
94+
/* 样式化通过 exportparts 暴露的按钮部分 */
95+
card-component::part(button) {
96+
background: #ff6b6b;
97+
border-color: #ff6b6b;
98+
border-radius: 25px;
99+
font-weight: bold;
100+
text-transform: uppercase;
101+
letter-spacing: 1px;
102+
}
103+
104+
card-component::part(button):hover {
105+
background: #ff5252;
106+
border-color: #ff5252;
107+
transform: translateY(-2px);
108+
box-shadow: 0 4px 8px rgba(255, 107, 107, 0.3);
109+
}
110+
111+
card-component::part(icon) {
112+
font-size: 20px;
113+
animation: bounce 2s infinite;
114+
}
115+
116+
card-component::part(text) {
117+
font-weight: bold;
118+
}
119+
120+
@keyframes bounce {
121+
0%, 20%, 50%, 80%, 100% {
122+
transform: translateY(0);
123+
}
124+
40% {
125+
transform: translateY(-3px);
126+
}
127+
60% {
128+
transform: translateY(-1px);
129+
}
130+
}
131+
132+
.demo-section {
133+
margin-bottom: 30px;
134+
}
135+
136+
.demo-title {
137+
font-size: 24px;
138+
margin-bottom: 15px;
139+
color: #333;
140+
border-bottom: 2px solid #667eea;
141+
padding-bottom: 5px;
142+
}
143+
144+
.description {
145+
background: #f8f9fa;
146+
padding: 15px;
147+
border-radius: 5px;
148+
margin-bottom: 20px;
149+
border-left: 4px solid #667eea;
150+
}
151+
`
152+
153+
render() {
154+
return (
155+
<div class="container">
156+
<div class="demo-section">
157+
<h1 class="demo-title">Omi ExportParts 示例</h1>
158+
159+
<div class="description">
160+
<p><strong>ExportParts</strong> 允许 Web Components 将内部组件的 CSS parts 暴露给外部,
161+
使得父组件可以通过 <code>::part()</code> 选择器来样式化嵌套组件的特定部分。</p>
162+
163+
<p>在这个示例中:</p>
164+
<ul>
165+
<li><code>inner-button</code> 定义了 <code>button</code><code>icon</code><code>text</code> 三个 parts</li>
166+
<li><code>card-component</code> 通过 <code>exportparts="button, icon, text"</code> 将这些 parts 暴露出来</li>
167+
<li><code>app-container</code> 可以通过 <code>card-component::part(button)</code> 等选择器来样式化这些部分</li>
168+
</ul>
169+
</div>
170+
171+
<card-component
172+
title="自定义样式卡片"
173+
buttonText="点击我">
174+
</card-component>
175+
176+
<card-component
177+
title="另一个卡片"
178+
buttonText="执行操作">
179+
</card-component>
180+
</div>
181+
</div>
182+
)
183+
}
184+
}
185+
186+
render(<app-container />, document.body)

0 commit comments

Comments
 (0)