@beppla/tapas-ui 1.4.8 → 1.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commonjs/StatisticsTable/README.md +75 -0
- package/commonjs/StatisticsTable/SORTING_FEATURE.md +271 -0
- package/commonjs/StatisticsTable/StatisticsTable.js +70 -8
- package/commonjs/StatisticsTable/StatisticsTable.js.map +1 -1
- package/module/StatisticsTable/README.md +75 -0
- package/module/StatisticsTable/SORTING_FEATURE.md +271 -0
- package/module/StatisticsTable/StatisticsTable.js +70 -8
- package/module/StatisticsTable/StatisticsTable.js.map +1 -1
- package/package.json +1 -1
- package/typescript/StatisticsTable/StatisticsTable.d.ts +4 -0
- package/typescript/StatisticsTable/StatisticsTable.d.ts.map +1 -1
|
@@ -265,6 +265,81 @@ For tables with hundreds or thousands of rows, enable virtual scrolling for opti
|
|
|
265
265
|
/>
|
|
266
266
|
```
|
|
267
267
|
|
|
268
|
+
## Sortable Columns
|
|
269
|
+
|
|
270
|
+
Add sorting functionality to column headers with built-in or custom rendering:
|
|
271
|
+
|
|
272
|
+
### Built-in Sorting UI
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
import { StatisticsTable } from '@beppla/tapas-ui';
|
|
276
|
+
|
|
277
|
+
function SortableTable() {
|
|
278
|
+
const [sortConfig, setSortConfig] = useState<{
|
|
279
|
+
column: string | null;
|
|
280
|
+
direction: 'asc' | 'desc' | null;
|
|
281
|
+
}>({ column: null, direction: null });
|
|
282
|
+
|
|
283
|
+
const handleSort = (columnKey: string) => {
|
|
284
|
+
setSortConfig(prev => ({
|
|
285
|
+
column: columnKey,
|
|
286
|
+
direction: prev.column === columnKey && prev.direction === 'asc'
|
|
287
|
+
? 'desc'
|
|
288
|
+
: 'asc'
|
|
289
|
+
}));
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const columns = [
|
|
293
|
+
{
|
|
294
|
+
key: 'store1',
|
|
295
|
+
title: 'Store A (€)',
|
|
296
|
+
width: 180,
|
|
297
|
+
sortable: true,
|
|
298
|
+
sortDirection: sortConfig.column === 'store1' ? sortConfig.direction : null,
|
|
299
|
+
onSort: handleSort,
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
key: 'store2',
|
|
303
|
+
title: 'Store B (€)',
|
|
304
|
+
width: 180,
|
|
305
|
+
sortable: true,
|
|
306
|
+
sortDirection: sortConfig.column === 'store2' ? sortConfig.direction : null,
|
|
307
|
+
onSort: handleSort,
|
|
308
|
+
},
|
|
309
|
+
];
|
|
310
|
+
|
|
311
|
+
return <StatisticsTable rows={rows} columns={columns} cells={cells} />;
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Custom Header Rendering
|
|
316
|
+
|
|
317
|
+
For complete control over header appearance:
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
const columns = [
|
|
321
|
+
{
|
|
322
|
+
key: 'store1',
|
|
323
|
+
title: 'Store A',
|
|
324
|
+
width: 180,
|
|
325
|
+
renderHeader: (column) => (
|
|
326
|
+
<TouchableOpacity
|
|
327
|
+
onPress={() => handleSort(column.key)}
|
|
328
|
+
style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}
|
|
329
|
+
>
|
|
330
|
+
<Text style={{ fontSize: 14, fontWeight: '600' }}>
|
|
331
|
+
{column.title}
|
|
332
|
+
</Text>
|
|
333
|
+
<View>
|
|
334
|
+
<Icon name="arrow-up" size={10} color={sortDirection === 'asc' ? '#000' : '#ccc'} />
|
|
335
|
+
<Icon name="arrow-down" size={10} color={sortDirection === 'desc' ? '#000' : '#ccc'} />
|
|
336
|
+
</View>
|
|
337
|
+
</TouchableOpacity>
|
|
338
|
+
),
|
|
339
|
+
},
|
|
340
|
+
];
|
|
341
|
+
```
|
|
342
|
+
|
|
268
343
|
## Internationalization (i18n)
|
|
269
344
|
|
|
270
345
|
Customize loading and empty state texts for different languages:
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# StatisticsTable 排序功能
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
StatisticsTable 组件支持两种方式实现列排序:
|
|
6
|
+
1. **内置排序 UI**:使用 `sortable`, `sortDirection`, `onSort` 属性
|
|
7
|
+
2. **完全自定义**:使用 `renderHeader` 属性完全控制表头渲染
|
|
8
|
+
|
|
9
|
+
## 方式一:内置排序 UI
|
|
10
|
+
|
|
11
|
+
### 功能特性
|
|
12
|
+
|
|
13
|
+
- ✅ 内置排序指示器(▲▼箭头)
|
|
14
|
+
- ✅ 自动高亮当前排序方向
|
|
15
|
+
- ✅ 点击表头切换排序
|
|
16
|
+
- ✅ 主题颜色自适应
|
|
17
|
+
|
|
18
|
+
### 使用示例
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import React, { useState } from 'react';
|
|
22
|
+
import { StatisticsTable } from '@beppla/tapas-ui';
|
|
23
|
+
|
|
24
|
+
function SortableTable() {
|
|
25
|
+
const [sortConfig, setSortConfig] = useState({
|
|
26
|
+
column: null,
|
|
27
|
+
direction: null,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const handleSort = (columnKey: string) => {
|
|
31
|
+
setSortConfig(prev => ({
|
|
32
|
+
column: columnKey,
|
|
33
|
+
direction: prev.column === columnKey && prev.direction === 'asc'
|
|
34
|
+
? 'desc'
|
|
35
|
+
: 'asc'
|
|
36
|
+
}));
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const columns = [
|
|
40
|
+
{
|
|
41
|
+
key: 'store1',
|
|
42
|
+
title: 'Store A (€)',
|
|
43
|
+
width: 180,
|
|
44
|
+
sortable: true, // 启用排序
|
|
45
|
+
sortDirection: sortConfig.column === 'store1' ? sortConfig.direction : null,
|
|
46
|
+
onSort: handleSort,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
key: 'store2',
|
|
50
|
+
title: 'Store B (€)',
|
|
51
|
+
width: 180,
|
|
52
|
+
sortable: true,
|
|
53
|
+
sortDirection: sortConfig.column === 'store2' ? sortConfig.direction : null,
|
|
54
|
+
onSort: handleSort,
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
// 对数据进行排序
|
|
59
|
+
let sortedRows = [...rows];
|
|
60
|
+
if (sortConfig.column && sortConfig.direction) {
|
|
61
|
+
sortedRows = sortedRows.sort((a, b) => {
|
|
62
|
+
const cellA = cells.find(c => c.rowKey === a.key && c.columnKey === sortConfig.column);
|
|
63
|
+
const cellB = cells.find(c => c.rowKey === b.key && c.columnKey === sortConfig.column);
|
|
64
|
+
const valueA = cellA?.amount || 0;
|
|
65
|
+
const valueB = cellB?.amount || 0;
|
|
66
|
+
return sortConfig.direction === 'asc' ? valueA - valueB : valueB - valueA;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<StatisticsTable
|
|
72
|
+
rows={sortedRows}
|
|
73
|
+
columns={columns}
|
|
74
|
+
cells={cells}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 内置 UI 说明
|
|
81
|
+
|
|
82
|
+
**排序指示器:**
|
|
83
|
+
- 两个箭头:▲ 升序,▼ 降序
|
|
84
|
+
- 未激活:浅灰色 `rgba(0, 0, 0, 0.26)`
|
|
85
|
+
- 激活时:深色 `rgba(0, 0, 0, 0.87)`
|
|
86
|
+
|
|
87
|
+
**点击行为:**
|
|
88
|
+
- 第一次点击:升序(▲ 高亮)
|
|
89
|
+
- 第二次点击:降序(▼ 高亮)
|
|
90
|
+
- 第三次点击:取消排序(视具体实现)
|
|
91
|
+
|
|
92
|
+
## 方式二:完全自定义 Header
|
|
93
|
+
|
|
94
|
+
### 使用 renderHeader
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
const columns = [
|
|
98
|
+
{
|
|
99
|
+
key: 'store1',
|
|
100
|
+
title: 'Store A (€)',
|
|
101
|
+
width: 200,
|
|
102
|
+
renderHeader: (column) => (
|
|
103
|
+
<TouchableOpacity
|
|
104
|
+
onPress={() => handleCustomSort(column.key)}
|
|
105
|
+
style={{
|
|
106
|
+
flexDirection: 'row',
|
|
107
|
+
alignItems: 'center',
|
|
108
|
+
gap: 8,
|
|
109
|
+
padding: 4,
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
<Text style={{
|
|
113
|
+
fontSize: 14,
|
|
114
|
+
fontWeight: '600',
|
|
115
|
+
color: '#333',
|
|
116
|
+
}}>
|
|
117
|
+
{column.title}
|
|
118
|
+
</Text>
|
|
119
|
+
|
|
120
|
+
{/* 自定义排序图标 */}
|
|
121
|
+
<View style={{ flexDirection: 'column', gap: 2 }}>
|
|
122
|
+
<TapasIcon
|
|
123
|
+
name="caret-up"
|
|
124
|
+
size={10}
|
|
125
|
+
color={currentSort.column === column.key && currentSort.dir === 'asc'
|
|
126
|
+
? '#895F38' // 品牌色
|
|
127
|
+
: '#CCC' // 灰色
|
|
128
|
+
}
|
|
129
|
+
/>
|
|
130
|
+
<TapasIcon
|
|
131
|
+
name="caret-down"
|
|
132
|
+
size={10}
|
|
133
|
+
color={currentSort.column === column.key && currentSort.dir === 'desc'
|
|
134
|
+
? '#895F38'
|
|
135
|
+
: '#CCC'
|
|
136
|
+
}
|
|
137
|
+
/>
|
|
138
|
+
</View>
|
|
139
|
+
</TouchableOpacity>
|
|
140
|
+
),
|
|
141
|
+
},
|
|
142
|
+
];
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 自定义样式示例
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
renderHeader: (column) => (
|
|
149
|
+
<View style={{
|
|
150
|
+
flexDirection: 'row',
|
|
151
|
+
alignItems: 'center',
|
|
152
|
+
justifyContent: 'space-between',
|
|
153
|
+
width: '100%',
|
|
154
|
+
}}>
|
|
155
|
+
<Text style={{ fontSize: 14, fontWeight: '700' }}>
|
|
156
|
+
{column.title}
|
|
157
|
+
</Text>
|
|
158
|
+
|
|
159
|
+
{/* 自定义徽章 */}
|
|
160
|
+
{column.key === 'total' && (
|
|
161
|
+
<View style={{
|
|
162
|
+
backgroundColor: '#F55523',
|
|
163
|
+
borderRadius: 12,
|
|
164
|
+
paddingHorizontal: 6,
|
|
165
|
+
paddingVertical: 2,
|
|
166
|
+
}}>
|
|
167
|
+
<Text style={{ fontSize: 10, color: '#FFF' }}>Total</Text>
|
|
168
|
+
</View>
|
|
169
|
+
)}
|
|
170
|
+
|
|
171
|
+
{/* 排序按钮 */}
|
|
172
|
+
<SortButton active={isSorted} direction={direction} />
|
|
173
|
+
</View>
|
|
174
|
+
)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## 新增属性
|
|
178
|
+
|
|
179
|
+
### StatisticsTableColumn
|
|
180
|
+
|
|
181
|
+
| 属性 | 类型 | 说明 |
|
|
182
|
+
|------|------|------|
|
|
183
|
+
| `sortable` | `boolean` | 是否可排序(启用内置排序 UI) |
|
|
184
|
+
| `sortDirection` | `'asc' \| 'desc' \| null` | 当前排序方向 |
|
|
185
|
+
| `onSort` | `(columnKey: string) => void` | 排序回调函数 |
|
|
186
|
+
| `renderHeader` | `(column) => ReactNode` | 完全自定义表头渲染 |
|
|
187
|
+
|
|
188
|
+
## 实现细节
|
|
189
|
+
|
|
190
|
+
### 内置排序渲染逻辑
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// 默认渲染(带排序支持)
|
|
194
|
+
const isSortable = column.sortable && column.onSort;
|
|
195
|
+
|
|
196
|
+
const HeaderContent = (
|
|
197
|
+
<View style={styles.headerContent}>
|
|
198
|
+
<Text style={styles.headerText}>
|
|
199
|
+
{column.title}
|
|
200
|
+
</Text>
|
|
201
|
+
{isSortable && (
|
|
202
|
+
<View style={styles.sortIndicator}>
|
|
203
|
+
<Text style={[
|
|
204
|
+
styles.sortArrow,
|
|
205
|
+
column.sortDirection === 'asc' && styles.sortArrowActive
|
|
206
|
+
]}>▲</Text>
|
|
207
|
+
<Text style={[
|
|
208
|
+
styles.sortArrow,
|
|
209
|
+
column.sortDirection === 'desc' && styles.sortArrowActive
|
|
210
|
+
]}>▼</Text>
|
|
211
|
+
</View>
|
|
212
|
+
)}
|
|
213
|
+
</View>
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<TouchableOpacity
|
|
218
|
+
onPress={isSortable ? () => column.onSort!(column.key) : undefined}
|
|
219
|
+
activeOpacity={isSortable ? 0.7 : 1}
|
|
220
|
+
disabled={!isSortable}
|
|
221
|
+
>
|
|
222
|
+
{HeaderContent}
|
|
223
|
+
</TouchableOpacity>
|
|
224
|
+
);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### 优先级
|
|
228
|
+
|
|
229
|
+
1. 如果提供了 `renderHeader`:使用完全自定义渲染
|
|
230
|
+
2. 如果设置了 `sortable` 和 `onSort`:使用内置排序 UI
|
|
231
|
+
3. 否则:显示普通文本标题
|
|
232
|
+
|
|
233
|
+
## 使用建议
|
|
234
|
+
|
|
235
|
+
### 何时使用内置排序 UI?
|
|
236
|
+
|
|
237
|
+
✅ **推荐使用:**
|
|
238
|
+
- 简单的表格排序需求
|
|
239
|
+
- 想要快速实现排序功能
|
|
240
|
+
- 满意内置的箭头样式
|
|
241
|
+
- 不需要特殊的排序图标
|
|
242
|
+
|
|
243
|
+
### 何时使用自定义 renderHeader?
|
|
244
|
+
|
|
245
|
+
✅ **推荐使用:**
|
|
246
|
+
- 需要特定的排序图标或样式
|
|
247
|
+
- 表头需要额外的元素(徽章、图标、提示等)
|
|
248
|
+
- 需要完全控制表头布局
|
|
249
|
+
- 需要与设计系统保持一致
|
|
250
|
+
|
|
251
|
+
## 注意事项
|
|
252
|
+
|
|
253
|
+
1. **数据排序逻辑**:组件本身不处理数据排序,需要用户自己实现排序逻辑
|
|
254
|
+
2. **状态管理**:排序状态需要用户管理(使用 useState)
|
|
255
|
+
3. **性能**:对于大数据集,建议配合虚拟滚动使用
|
|
256
|
+
4. **占位列**:占位列会自动跳过排序功能
|
|
257
|
+
|
|
258
|
+
## Storybook 示例
|
|
259
|
+
|
|
260
|
+
- `SortableColumns` - 内置排序 UI 演示
|
|
261
|
+
- `CustomHeaderRendering` - 完全自定义表头演示
|
|
262
|
+
|
|
263
|
+
## 相关更新
|
|
264
|
+
|
|
265
|
+
- ✅ StatisticsTableColumn 接口添加排序相关属性
|
|
266
|
+
- ✅ 表头支持点击事件
|
|
267
|
+
- ✅ 内置排序指示器样式
|
|
268
|
+
- ✅ 完整的自定义渲染支持
|
|
269
|
+
- ✅ 2 个 Storybook 示例
|
|
270
|
+
- ✅ 2 个测试用例
|
|
271
|
+
|
|
@@ -227,11 +227,11 @@ function StatisticsTable({
|
|
|
227
227
|
},
|
|
228
228
|
// Hover 行
|
|
229
229
|
rowHovered: {
|
|
230
|
-
backgroundColor: colors.
|
|
230
|
+
backgroundColor: colors.colorSurface8 // rgba(0, 0, 0, 0.04) - 更柔和
|
|
231
231
|
},
|
|
232
|
-
// Hover 单元格
|
|
232
|
+
// Hover 单元格
|
|
233
233
|
cellHovered: {
|
|
234
|
-
backgroundColor:
|
|
234
|
+
backgroundColor: colors.colorSurface8 // rgba(0, 0, 0, 0.04) - 更柔和
|
|
235
235
|
},
|
|
236
236
|
// Column Stats 行(Sum/Mean)- 纯白色背景
|
|
237
237
|
columnStatsRow: {
|
|
@@ -673,6 +673,20 @@ function StatisticsTable({
|
|
|
673
673
|
onScroll: handleScroll,
|
|
674
674
|
scrollEventThrottle: 16,
|
|
675
675
|
style: hideScrollbarStyle,
|
|
676
|
+
onContentSizeChange: () => {
|
|
677
|
+
// 当内容大小变化时,检查是否需要显示滚动阴影
|
|
678
|
+
if (scrollViewRef.current && enableScrollShadow) {
|
|
679
|
+
// React Native 会在 contentSize 变化后自动触发 onScroll
|
|
680
|
+
// 但为了确保状态正确,我们手动触发一次
|
|
681
|
+
setTimeout(() => {
|
|
682
|
+
scrollViewRef.current?.scrollTo?.({
|
|
683
|
+
x: 0,
|
|
684
|
+
y: 0,
|
|
685
|
+
animated: false
|
|
686
|
+
});
|
|
687
|
+
}, 0);
|
|
688
|
+
}
|
|
689
|
+
},
|
|
676
690
|
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
677
691
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
678
692
|
style: [styles.header, themedStyles.header],
|
|
@@ -685,14 +699,44 @@ function StatisticsTable({
|
|
|
685
699
|
})
|
|
686
700
|
}), displayColumns.map(column => {
|
|
687
701
|
const colWidth = getColWidth(column);
|
|
688
|
-
|
|
702
|
+
const isPlaceholder = column.key.startsWith('_placeholder_col_');
|
|
703
|
+
|
|
704
|
+
// 如果提供了自定义 header 渲染函数
|
|
705
|
+
if (column.renderHeader && !isPlaceholder) {
|
|
706
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
707
|
+
style: [styles.headerCell, themedStyles.headerCell, {
|
|
708
|
+
width: colWidth
|
|
709
|
+
}],
|
|
710
|
+
children: column.renderHeader(column)
|
|
711
|
+
}, column.key);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// 默认渲染(带排序支持)
|
|
715
|
+
const isSortable = column.sortable && column.onSort;
|
|
716
|
+
const HeaderContent = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
717
|
+
style: styles.headerContent,
|
|
718
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
719
|
+
style: [styles.headerText, themedStyles.headerText],
|
|
720
|
+
children: column.title
|
|
721
|
+
}), isSortable && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
722
|
+
style: styles.sortIndicator,
|
|
723
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
724
|
+
style: [styles.sortArrow, column.sortDirection === 'asc' && styles.sortArrowActive],
|
|
725
|
+
children: "\u25B2"
|
|
726
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
727
|
+
style: [styles.sortArrow, column.sortDirection === 'desc' && styles.sortArrowActive],
|
|
728
|
+
children: "\u25BC"
|
|
729
|
+
})]
|
|
730
|
+
})]
|
|
731
|
+
});
|
|
732
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
689
733
|
style: [styles.headerCell, themedStyles.headerCell, {
|
|
690
734
|
width: colWidth
|
|
691
735
|
}],
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
736
|
+
onPress: isSortable ? () => column.onSort(column.key) : undefined,
|
|
737
|
+
activeOpacity: isSortable ? 0.7 : 1,
|
|
738
|
+
disabled: !isSortable,
|
|
739
|
+
children: HeaderContent
|
|
696
740
|
}, column.key);
|
|
697
741
|
})]
|
|
698
742
|
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
|
|
@@ -1019,6 +1063,24 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
1019
1063
|
fontWeight: '500',
|
|
1020
1064
|
textAlign: 'center'
|
|
1021
1065
|
},
|
|
1066
|
+
headerContent: {
|
|
1067
|
+
flexDirection: 'row',
|
|
1068
|
+
alignItems: 'center',
|
|
1069
|
+
justifyContent: 'center',
|
|
1070
|
+
gap: 6
|
|
1071
|
+
},
|
|
1072
|
+
sortIndicator: {
|
|
1073
|
+
flexDirection: 'column',
|
|
1074
|
+
gap: 2
|
|
1075
|
+
},
|
|
1076
|
+
sortArrow: {
|
|
1077
|
+
fontSize: 8,
|
|
1078
|
+
color: 'rgba(0, 0, 0, 0.26)',
|
|
1079
|
+
lineHeight: 8
|
|
1080
|
+
},
|
|
1081
|
+
sortArrowActive: {
|
|
1082
|
+
color: 'rgba(0, 0, 0, 0.87)'
|
|
1083
|
+
},
|
|
1022
1084
|
row: {
|
|
1023
1085
|
flexDirection: 'row',
|
|
1024
1086
|
borderBottomWidth: 1
|