@citizenplane/pimp 18.9.36 → 18.10.1
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/dist/components/CpTabs.vue.d.ts +7 -1
- package/dist/components/CpTabs.vue.d.ts.map +1 -1
- package/dist/pimp.es.js +643 -581
- package/dist/pimp.umd.js +2 -2
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/CpTabs.vue +242 -48
- package/src/stories/CpTabs.stories.ts +23 -10
package/package.json
CHANGED
|
@@ -1,31 +1,38 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div ref="cpTabsElement" class="cpTabs" role="tablist">
|
|
2
|
+
<div ref="cpTabsElement" class="cpTabs" :class="dynamicTabsClass" role="tablist">
|
|
3
3
|
<button
|
|
4
4
|
v-for="(tab, index) in tabs"
|
|
5
5
|
:key="tab.title"
|
|
6
6
|
class="cpTabs__tab"
|
|
7
7
|
:class="getTabClass(index)"
|
|
8
|
+
:aria-selected="isActiveTab(index)"
|
|
8
9
|
role="tab"
|
|
9
|
-
tabindex="
|
|
10
|
+
:tabindex="getTabIndex(index)"
|
|
10
11
|
type="button"
|
|
11
12
|
@click="handleTabClick(index)"
|
|
13
|
+
@keydown="handleTabKeydown($event, index)"
|
|
12
14
|
>
|
|
13
|
-
<cp-icon v-if="tab.
|
|
14
|
-
<cp-
|
|
15
|
+
<cp-icon v-if="tab.leadingIcon" class="cpTabs__icon" :class="dynamicIconClass" :type="tab.leadingIcon" />
|
|
16
|
+
<cp-text class="cpTabs__label" tag="span" :size="textSize">
|
|
15
17
|
{{ tab.title }}
|
|
16
|
-
</cp-
|
|
18
|
+
</cp-text>
|
|
17
19
|
<div v-if="hasTabCount(tab.count)" class="cpTabs__count">
|
|
18
|
-
<cp-badge :color="dynamicBadgeColor(index)" size="
|
|
20
|
+
<cp-badge :color="dynamicBadgeColor(index)" :variant="dynamicBadgeVariant(index)" :size="badgeSize">
|
|
19
21
|
{{ tab.count }}
|
|
20
22
|
</cp-badge>
|
|
21
23
|
</div>
|
|
24
|
+
<cp-icon v-if="tab.trailingIcon" class="cpTabs__icon" :class="dynamicIconClass" :type="tab.trailingIcon" />
|
|
22
25
|
</button>
|
|
23
|
-
<div ref="activeUnderline" class="cpTabs__activeUnderline" />
|
|
26
|
+
<div ref="activeUnderline" class="cpTabs__activeUnderline" :class="dynamicUnderlineClass" />
|
|
24
27
|
</div>
|
|
25
28
|
</template>
|
|
26
29
|
|
|
27
30
|
<script setup lang="ts">
|
|
28
|
-
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
31
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
32
|
+
|
|
33
|
+
import CpText from '@/components/CpText.vue'
|
|
34
|
+
|
|
35
|
+
import { capitalizeFirstLetter } from '@/helpers'
|
|
29
36
|
|
|
30
37
|
type EmitType = {
|
|
31
38
|
(e: 'onTabClick', index: number): void
|
|
@@ -33,39 +40,133 @@ type EmitType = {
|
|
|
33
40
|
|
|
34
41
|
interface Props {
|
|
35
42
|
defaultActiveIndex?: number
|
|
43
|
+
isFullWidth?: boolean
|
|
36
44
|
isLoading?: boolean
|
|
45
|
+
size?: 'xs' | 'sm' | 'md' | 'lg'
|
|
37
46
|
tabs: {
|
|
38
47
|
count?: number
|
|
39
|
-
|
|
48
|
+
leadingIcon?: string
|
|
40
49
|
title: string
|
|
50
|
+
trailingIcon?: string
|
|
41
51
|
}[]
|
|
52
|
+
variant?: 'brand' | 'underline'
|
|
42
53
|
}
|
|
43
54
|
|
|
44
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
55
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
56
|
+
defaultActiveIndex: 0,
|
|
57
|
+
size: 'md',
|
|
58
|
+
variant: 'underline'
|
|
59
|
+
})
|
|
45
60
|
|
|
46
61
|
const emit = defineEmits<EmitType>()
|
|
47
62
|
|
|
48
63
|
const cpTabsElement = ref<HTMLElement | null>(null)
|
|
49
64
|
const activeUnderline = ref<HTMLElement | null>(null)
|
|
50
65
|
const activeTabIndex = ref<number | null>(props.defaultActiveIndex)
|
|
66
|
+
const suppressUnderlineTransition = ref(false)
|
|
67
|
+
|
|
68
|
+
const textSize = computed(() => {
|
|
69
|
+
switch (props.size) {
|
|
70
|
+
case 'xs':
|
|
71
|
+
case 'sm':
|
|
72
|
+
return 'xs'
|
|
73
|
+
case 'md':
|
|
74
|
+
case 'lg':
|
|
75
|
+
default:
|
|
76
|
+
return 'md'
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const badgeSize = computed(() => {
|
|
81
|
+
switch (props.size) {
|
|
82
|
+
case 'xs':
|
|
83
|
+
case 'sm':
|
|
84
|
+
return '2xs'
|
|
85
|
+
case 'md':
|
|
86
|
+
case 'lg':
|
|
87
|
+
default:
|
|
88
|
+
return 'xs'
|
|
89
|
+
}
|
|
90
|
+
})
|
|
51
91
|
|
|
52
92
|
const hasTabCount = (count?: number) => typeof count === 'number'
|
|
53
93
|
|
|
54
|
-
const
|
|
94
|
+
const isActiveTab = (index: number) => activeTabIndex.value === index
|
|
95
|
+
|
|
96
|
+
const getTabIndex = (index: number) => isActiveTab(index) ? 0 : -1
|
|
97
|
+
|
|
98
|
+
const activateTab = async (index: number, { focus = false } = {}) => {
|
|
55
99
|
if (props.isLoading) return
|
|
100
|
+
if (index < 0 || index >= props.tabs.length) return
|
|
101
|
+
|
|
102
|
+
if (focus) suppressUnderlineTransition.value = true
|
|
56
103
|
|
|
57
104
|
activeTabIndex.value = index
|
|
58
105
|
emit('onTabClick', index)
|
|
59
|
-
scrollToActiveElement()
|
|
106
|
+
scrollToActiveElement(focus ? 'instant' : 'smooth')
|
|
107
|
+
|
|
108
|
+
if (!focus) return
|
|
109
|
+
|
|
110
|
+
await nextTick()
|
|
111
|
+
getActiveTabElement()?.focus()
|
|
112
|
+
requestAnimationFrame(() => (suppressUnderlineTransition.value = false))
|
|
60
113
|
}
|
|
61
114
|
|
|
115
|
+
const handleTabClick = (index: number) => activateTab(index)
|
|
116
|
+
|
|
117
|
+
const handleTabKeydown = (event: KeyboardEvent, index: number) => {
|
|
118
|
+
if (props.isLoading) return
|
|
119
|
+
|
|
120
|
+
const lastIndex = props.tabs.length - 1
|
|
121
|
+
let nextIndex: number | null = null
|
|
122
|
+
|
|
123
|
+
switch (event.key) {
|
|
124
|
+
case 'ArrowRight':
|
|
125
|
+
nextIndex = index === lastIndex ? 0 : index + 1
|
|
126
|
+
break
|
|
127
|
+
case 'ArrowLeft':
|
|
128
|
+
nextIndex = index === 0 ? lastIndex : index - 1
|
|
129
|
+
break
|
|
130
|
+
default:
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
event.preventDefault()
|
|
135
|
+
activateTab(nextIndex, { focus: true })
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const dynamicIconClass = computed(() => {
|
|
139
|
+
return { [`cpTabs__icon--${props.size}`]: true }
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const dynamicTabsClass = computed(() => {
|
|
143
|
+
return {
|
|
144
|
+
[`cpTabs--${props.size}`]: true,
|
|
145
|
+
[`cpTabs--is${capitalizeFirstLetter(props.variant)}`]: true,
|
|
146
|
+
'cpTabs--isFullWidth': props.isFullWidth,
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
62
150
|
const getTabClass = (tabIndex: number) => {
|
|
63
151
|
if (!props.tabs[tabIndex]) return
|
|
64
152
|
|
|
65
|
-
return
|
|
153
|
+
return {
|
|
154
|
+
'cpTabs__tab--isActive': activeTabIndex.value === tabIndex,
|
|
155
|
+
'cpTabs__tab--isLoading': props.isLoading,
|
|
156
|
+
[`cpTabs__tab--${props.size}`]: true,
|
|
157
|
+
}
|
|
66
158
|
}
|
|
67
159
|
|
|
68
|
-
const
|
|
160
|
+
const dynamicUnderlineClass = computed(() => {
|
|
161
|
+
return {
|
|
162
|
+
[`cpTabs__activeUnderline--is${capitalizeFirstLetter(props.variant)}`]: true,
|
|
163
|
+
'cpTabs__activeUnderline--noTransition': suppressUnderlineTransition.value
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
const dynamicBadgeColor = (index: number) => index === activeTabIndex.value ? 'accent' : 'neutral'
|
|
168
|
+
|
|
169
|
+
const dynamicBadgeVariant = (index: number) => index === activeTabIndex.value ? 'solid' : 'soft'
|
|
69
170
|
|
|
70
171
|
const getActiveTabElement = () => {
|
|
71
172
|
const componentElement = cpTabsElement.value
|
|
@@ -83,6 +184,17 @@ const getUnderlineElement = () => {
|
|
|
83
184
|
return underlineElement
|
|
84
185
|
}
|
|
85
186
|
|
|
187
|
+
// NOTE: fix mimatch width between active underline & focus outline of active tab
|
|
188
|
+
const getUnderlineInset = () => {
|
|
189
|
+
if (props.variant !== 'underline') return 0
|
|
190
|
+
|
|
191
|
+
const rootStyle = getComputedStyle(document.documentElement)
|
|
192
|
+
const rootFontSize = Number.parseFloat(rootStyle.fontSize)
|
|
193
|
+
const insetRem = Number.parseFloat(rootStyle.getPropertyValue('--cp-dimensions-0_5'))
|
|
194
|
+
|
|
195
|
+
return rootFontSize * insetRem
|
|
196
|
+
}
|
|
197
|
+
|
|
86
198
|
const setUnderlineStyle = () => {
|
|
87
199
|
const activeTabElement = getActiveTabElement()
|
|
88
200
|
const underlineElement = getUnderlineElement()
|
|
@@ -90,15 +202,17 @@ const setUnderlineStyle = () => {
|
|
|
90
202
|
|
|
91
203
|
if (!activeTabElement || !underlineElement || !containerElement) return
|
|
92
204
|
|
|
93
|
-
|
|
94
|
-
|
|
205
|
+
const inset = getUnderlineInset()
|
|
206
|
+
|
|
207
|
+
underlineElement.style.width = `${activeTabElement.offsetWidth - inset * 2}px`
|
|
208
|
+
underlineElement.style.transform = `translate3d(${activeTabElement.offsetLeft + inset}px, 0, 0)`
|
|
95
209
|
}
|
|
96
210
|
|
|
97
|
-
const scrollToActiveElement = () => {
|
|
211
|
+
const scrollToActiveElement = (behavior: ScrollBehavior = 'smooth') => {
|
|
98
212
|
const activeTabElement = getActiveTabElement()
|
|
99
213
|
if (!activeTabElement) return
|
|
100
214
|
|
|
101
|
-
return activeTabElement.scrollIntoView({ block: 'nearest', behavior
|
|
215
|
+
return activeTabElement.scrollIntoView({ block: 'nearest', behavior })
|
|
102
216
|
}
|
|
103
217
|
|
|
104
218
|
onBeforeUnmount(() => window.removeEventListener('resize', setUnderlineStyle))
|
|
@@ -122,30 +236,62 @@ watch(
|
|
|
122
236
|
|
|
123
237
|
position: relative;
|
|
124
238
|
display: flex;
|
|
239
|
+
width: 100%;
|
|
125
240
|
align-items: center;
|
|
126
|
-
|
|
241
|
+
|
|
242
|
+
&--xs {
|
|
243
|
+
gap: var(--cp-spacing-xs);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
&--sm {
|
|
247
|
+
gap: var(--cp-spacing-sm);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
&--md {
|
|
251
|
+
gap: var(--cp-spacing-lg);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
&--lg {
|
|
255
|
+
gap: var(--cp-spacing-lg);
|
|
256
|
+
}
|
|
127
257
|
|
|
128
258
|
&__activeUnderline {
|
|
129
259
|
position: absolute;
|
|
130
|
-
|
|
131
|
-
left: 0;
|
|
132
|
-
height: var(--cp-dimensions-0_5);
|
|
260
|
+
z-index: 0;
|
|
133
261
|
transition:
|
|
134
262
|
background-color 300ms ease,
|
|
135
263
|
transform 300ms cubic-bezier(0.34, 1.26, 0.64, 1),
|
|
136
264
|
width 300ms cubic-bezier(0.34, 1.26, 0.64, 1);
|
|
265
|
+
|
|
266
|
+
&--noTransition {
|
|
267
|
+
transition: none;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
&--isBrand {
|
|
271
|
+
top: auto;
|
|
272
|
+
bottom: auto;
|
|
273
|
+
height: 100%;
|
|
274
|
+
border-radius: var(--cp-radius-md);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
&--isUnderline {
|
|
278
|
+
bottom: 0;
|
|
279
|
+
left: 0;
|
|
280
|
+
height: var(--cp-dimensions-0_5);
|
|
281
|
+
margin-bottom: -1px;
|
|
282
|
+
}
|
|
137
283
|
}
|
|
138
284
|
|
|
139
285
|
&__tab {
|
|
140
286
|
@extend %u-focus-outline;
|
|
141
287
|
|
|
142
288
|
position: relative;
|
|
289
|
+
z-index: 1;
|
|
143
290
|
display: flex;
|
|
144
291
|
align-items: center;
|
|
145
|
-
|
|
292
|
+
justify-content: center;
|
|
146
293
|
border-radius: var(--cp-radius-md);
|
|
147
294
|
color: var(--cp-text-secondary);
|
|
148
|
-
gap: var(--cp-spacing-sm);
|
|
149
295
|
-webkit-tap-highlight-color: transparent;
|
|
150
296
|
transition:
|
|
151
297
|
box-shadow 300ms ease,
|
|
@@ -164,20 +310,79 @@ watch(
|
|
|
164
310
|
color: var(--cp-text-accent-primary);
|
|
165
311
|
}
|
|
166
312
|
|
|
167
|
-
&--isActive ~ .cpTabs__activeUnderline {
|
|
168
|
-
background-color: var(--cp-
|
|
313
|
+
&--isActive ~ .cpTabs__activeUnderline--isBrand {
|
|
314
|
+
background-color: var(--cp-background-accent-secondary);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
&--isActive ~ .cpTabs__activeUnderline--isUnderline {
|
|
318
|
+
background-color: var(--cp-background-accent-solid);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
&--xs {
|
|
322
|
+
padding: var(--cp-spacing-sm-md);
|
|
323
|
+
gap: var(--cp-spacing-xs);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
&--sm {
|
|
327
|
+
padding: var(--cp-spacing-md);
|
|
328
|
+
gap: var(--cp-spacing-sm);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
&--md {
|
|
332
|
+
padding: var(--cp-spacing-md) var(--cp-spacing-lg);
|
|
333
|
+
gap: var(--cp-spacing-sm-md);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
&--lg {
|
|
337
|
+
padding: var(--cp-spacing-lg) var(--cp-spacing-xl);
|
|
338
|
+
gap: var(--cp-spacing-sm-md);
|
|
169
339
|
}
|
|
170
340
|
}
|
|
171
341
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
342
|
+
&--isUnderline {
|
|
343
|
+
border-bottom: 1px solid var(--cp-border-soft);
|
|
344
|
+
|
|
345
|
+
.cpTabs__tab {
|
|
346
|
+
margin-bottom: -1px;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.cpTabs__tab:focus-visible {
|
|
350
|
+
border-bottom-left-radius: 0;
|
|
351
|
+
border-bottom-right-radius: 0;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
&--isFullWidth {
|
|
356
|
+
.cpTabs__tab {
|
|
357
|
+
flex: 1;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
&__label {
|
|
177
362
|
transition: color 300ms ease;
|
|
178
363
|
white-space: nowrap;
|
|
179
364
|
}
|
|
180
365
|
|
|
366
|
+
&__label--xs {
|
|
367
|
+
font-size: var(--cp-text-size-xs);
|
|
368
|
+
line-height: var(--cp-line-height-xs);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
&__label--sm {
|
|
372
|
+
font-size: var(--cp-text-size-sm);
|
|
373
|
+
line-height: var(--cp-line-height-sm);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
&__label--md {
|
|
377
|
+
font-size: var(--cp-text-size-md);
|
|
378
|
+
line-height: var(--cp-line-height-md);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
&__label--lg {
|
|
382
|
+
font-size: var(--cp-text-size-lg);
|
|
383
|
+
line-height: var(--cp-line-height-lg);
|
|
384
|
+
}
|
|
385
|
+
|
|
181
386
|
&__count {
|
|
182
387
|
font-variant: tabular-nums;
|
|
183
388
|
}
|
|
@@ -186,26 +391,15 @@ watch(
|
|
|
186
391
|
position: relative;
|
|
187
392
|
display: flex;
|
|
188
393
|
border-radius: var(--cp-radius-sm);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
394
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
border-bottom-left-radius: 0;
|
|
196
|
-
border-bottom-right-radius: 0;
|
|
395
|
+
&--xs,
|
|
396
|
+
&--sm {
|
|
397
|
+
@include mx.square-sizing(16);
|
|
197
398
|
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
399
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
top: auto;
|
|
205
|
-
bottom: auto;
|
|
206
|
-
height: 100%;
|
|
207
|
-
border-radius: var(--cp-radius-md);
|
|
208
|
-
opacity: 0.14;
|
|
400
|
+
&--md,
|
|
401
|
+
&--lg {
|
|
402
|
+
@include mx.square-sizing(20);
|
|
209
403
|
}
|
|
210
404
|
}
|
|
211
405
|
}
|
|
@@ -20,6 +20,16 @@ const meta = {
|
|
|
20
20
|
control: 'object',
|
|
21
21
|
description: 'Array of tab objects with title, optional icon, and optional count',
|
|
22
22
|
},
|
|
23
|
+
size: {
|
|
24
|
+
control: 'select',
|
|
25
|
+
options: ['xs', 'sm', 'md', 'lg'],
|
|
26
|
+
description: 'The size of the tabs',
|
|
27
|
+
},
|
|
28
|
+
variant: {
|
|
29
|
+
control: 'select',
|
|
30
|
+
options: ['brand', 'underline'],
|
|
31
|
+
description: 'The variant of the tabs',
|
|
32
|
+
},
|
|
23
33
|
},
|
|
24
34
|
} satisfies Meta<typeof CpTabs>
|
|
25
35
|
|
|
@@ -32,7 +42,10 @@ const defaultRender = (args: Args) => ({
|
|
|
32
42
|
setup() {
|
|
33
43
|
return { args }
|
|
34
44
|
},
|
|
35
|
-
template:
|
|
45
|
+
template: `
|
|
46
|
+
<CpTabs v-bind="args" @on-tab-click="args.onOnTabClick" variant="underline" /><br />
|
|
47
|
+
<CpTabs v-bind="args" @on-tab-click="args.onOnTabClick" variant="brand" />
|
|
48
|
+
`,
|
|
36
49
|
})
|
|
37
50
|
|
|
38
51
|
/**
|
|
@@ -57,9 +70,9 @@ export const WithIcons: Story = {
|
|
|
57
70
|
args: {
|
|
58
71
|
...Default.args,
|
|
59
72
|
tabs: [
|
|
60
|
-
{ title: 'Home',
|
|
61
|
-
{ title: 'Settings',
|
|
62
|
-
{ title: 'User',
|
|
73
|
+
{ title: 'Home', leadingIcon: 'circle', trailingIcon: 'circle' },
|
|
74
|
+
{ title: 'Settings', leadingIcon: 'circle', trailingIcon: 'circle' },
|
|
75
|
+
{ title: 'User', leadingIcon: 'circle', trailingIcon: 'circle' },
|
|
63
76
|
],
|
|
64
77
|
},
|
|
65
78
|
render: defaultRender,
|
|
@@ -88,9 +101,9 @@ export const WithIconsAndCounts: Story = {
|
|
|
88
101
|
args: {
|
|
89
102
|
...Default.args,
|
|
90
103
|
tabs: [
|
|
91
|
-
{ title: 'Notifications',
|
|
92
|
-
{ title: 'Messages',
|
|
93
|
-
{ title: 'Tasks',
|
|
104
|
+
{ title: 'Notifications', leadingIcon: 'circle', trailingIcon: 'circle', count: 8 },
|
|
105
|
+
{ title: 'Messages', leadingIcon: 'circle', trailingIcon: 'circle', count: 3 },
|
|
106
|
+
{ title: 'Tasks', leadingIcon: 'circle', trailingIcon: 'circle', count: 15 },
|
|
94
107
|
],
|
|
95
108
|
},
|
|
96
109
|
render: defaultRender,
|
|
@@ -105,9 +118,9 @@ export const InDialog: Story = {
|
|
|
105
118
|
...Default.args,
|
|
106
119
|
defaultActiveIndex: 1,
|
|
107
120
|
tabs: [
|
|
108
|
-
{ title: 'Notifications',
|
|
109
|
-
{ title: 'Messages',
|
|
110
|
-
{ title: 'Tasks',
|
|
121
|
+
{ title: 'Notifications', leadingIcon: 'circle', trailingIcon: 'circle', count: 8 },
|
|
122
|
+
{ title: 'Messages', leadingIcon: 'circle', trailingIcon: 'circle', count: 3 },
|
|
123
|
+
{ title: 'Tasks', leadingIcon: 'circle', trailingIcon: 'circle', count: 15 },
|
|
111
124
|
],
|
|
112
125
|
},
|
|
113
126
|
render: (args) => ({
|