atomsystem 0.4.3 → 0.5.0
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/README.md +112 -52
- package/components/blocks/BlockDetailPageContent.tsx +337 -0
- package/components/blocks/BlockViewerCard.tsx +123 -123
- package/components/blocks/BlocksSidebar.tsx +93 -0
- package/components/blocks/CardListBlock.tsx +101 -196
- package/components/blocks/FileExplorerBlock.tsx +114 -446
- package/components/blocks/InteractiveChatBlock.tsx +107 -123
- package/components/blocks/MetricStatsBlock.tsx +78 -106
- package/components/blocks/PricingPlanBlock.tsx +88 -144
- package/components/blocks/RealtimeChartBlock.tsx +131 -250
- package/components/blocks/SliderShowcaseBlock.tsx +105 -305
- package/components/ui/Container.tsx +45 -0
- package/components/ui/Layout.tsx +220 -0
- package/components/ui/Switch.tsx +39 -0
- package/components/ui/Table.tsx +88 -0
- package/components/ui/Tabs.tsx +58 -0
- package/components/ui/index.ts +5 -0
- package/css/atom-ui.css +41 -816
- package/dist/cli.cjs +633 -41
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +630 -39
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +176 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -4
- package/dist/index.d.ts +13 -4
- package/dist/index.js +176 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/presets/agents.guide.md +49 -0
- package/presets/blocks.md +101 -0
- package/presets/components.md +142 -0
- package/tokens/token.json +63 -1
- package/tokens/tokens.json +63 -1
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import React, { useState } from 'react';
|
|
4
|
+
import { Card, Button, Badge } from '@/components/ui';
|
|
4
5
|
import {
|
|
5
6
|
LayoutGrid,
|
|
6
7
|
List as ListIcon,
|
|
7
8
|
Search,
|
|
8
9
|
Bookmark,
|
|
9
|
-
Star,
|
|
10
|
-
Eye,
|
|
11
10
|
ArrowUpRight,
|
|
12
|
-
Sparkles,
|
|
13
|
-
Layers,
|
|
14
11
|
Palette,
|
|
15
|
-
|
|
12
|
+
Layers,
|
|
13
|
+
Shield,
|
|
16
14
|
Zap,
|
|
17
15
|
} from 'lucide-react';
|
|
18
16
|
|
|
@@ -22,94 +20,61 @@ interface CardItem {
|
|
|
22
20
|
title: string;
|
|
23
21
|
description: string;
|
|
24
22
|
tag: string;
|
|
23
|
+
price: string;
|
|
25
24
|
icon: 'palette' | 'layers' | 'shield' | 'zap';
|
|
26
|
-
stats: { views: string; rating: number };
|
|
27
|
-
date: string;
|
|
28
|
-
isBookmarked?: boolean;
|
|
29
25
|
}
|
|
30
26
|
|
|
31
27
|
const INITIAL_CARDS: CardItem[] = [
|
|
32
28
|
{
|
|
33
29
|
id: 'c1',
|
|
34
30
|
category: 'Foundation',
|
|
35
|
-
title: '시맨틱 색상
|
|
36
|
-
description: 'W3C DTCG 표준 3단계 계층
|
|
31
|
+
title: '시맨틱 색상 & 다크모드 매트릭스',
|
|
32
|
+
description: 'W3C DTCG 표준 3단계 계층 구조를 준수하는 토큰 기반 컬러 시스템.',
|
|
37
33
|
tag: 'W3C 표준',
|
|
34
|
+
price: 'Free',
|
|
38
35
|
icon: 'palette',
|
|
39
|
-
stats: { views: '14.2k', rating: 4.9 },
|
|
40
|
-
date: '2026.11',
|
|
41
36
|
},
|
|
42
37
|
{
|
|
43
38
|
id: 'c2',
|
|
44
39
|
category: 'Component',
|
|
45
40
|
title: '고접근성 인터랙티브 모달 & 드로어',
|
|
46
|
-
description: '
|
|
41
|
+
description: '포커스 트랩과 키보드 내비게이션을 보장하는 시맨틱 레이어 컴포넌트.',
|
|
47
42
|
tag: 'A11y AAA',
|
|
43
|
+
price: '$19',
|
|
48
44
|
icon: 'layers',
|
|
49
|
-
stats: { views: '9.8k', rating: 4.8 },
|
|
50
|
-
date: '2026.10',
|
|
51
45
|
},
|
|
52
46
|
{
|
|
53
47
|
id: 'c3',
|
|
54
48
|
category: 'Pattern',
|
|
55
|
-
title: 'B2B 핀테크 인증
|
|
56
|
-
description: '
|
|
49
|
+
title: 'B2B 핀테크 인증 & 결제 승인 플로우',
|
|
50
|
+
description: '실시간 유효성 검증과 가맹점 수수료율 자동 산출 반응형 위젯.',
|
|
57
51
|
tag: 'Fintech UX',
|
|
52
|
+
price: '$39',
|
|
58
53
|
icon: 'shield',
|
|
59
|
-
stats: { views: '18.5k', rating: 5.0 },
|
|
60
|
-
date: '2026.11',
|
|
61
54
|
},
|
|
62
55
|
{
|
|
63
56
|
id: 'c4',
|
|
64
57
|
category: 'Component',
|
|
65
|
-
title: '
|
|
66
|
-
description: '
|
|
58
|
+
title: '가변 폰트 & 타이포 스케일러',
|
|
59
|
+
description: 'Fluid 폰트 크기 및 행간을 토큰으로 제어하는 타이포그래피 블록.',
|
|
67
60
|
tag: 'Typography',
|
|
61
|
+
price: 'Free',
|
|
68
62
|
icon: 'zap',
|
|
69
|
-
stats: { views: '7.1k', rating: 4.7 },
|
|
70
|
-
date: '2026.09',
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
id: 'c5',
|
|
74
|
-
category: 'Pattern',
|
|
75
|
-
title: 'AI 에이전트 스트리밍 메시지 뷰포트',
|
|
76
|
-
description: '대규모 언어 모델의 토큰 단위 스트리밍을 60fps로 매끄럽게 자동 스크롤하는 차세대 피드.',
|
|
77
|
-
tag: 'AI System',
|
|
78
|
-
icon: 'layers',
|
|
79
|
-
stats: { views: '23.4k', rating: 4.9 },
|
|
80
|
-
date: '2026.11',
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
id: 'c6',
|
|
84
|
-
category: 'Foundation',
|
|
85
|
-
title: '모션 이징 & 스프링 물리 타이밍 토큰',
|
|
86
|
-
description: '자연스러운 사용자 경험을 위한 GSAP 및 CSS transition 타이밍 커브 세트.',
|
|
87
|
-
tag: 'Micro Interaction',
|
|
88
|
-
icon: 'palette',
|
|
89
|
-
stats: { views: '11.0k', rating: 4.8 },
|
|
90
|
-
date: '2026.10',
|
|
91
63
|
},
|
|
92
64
|
];
|
|
93
65
|
|
|
94
66
|
export function CardListBlock() {
|
|
95
67
|
const [cards, setCards] = useState<CardItem[]>(INITIAL_CARDS);
|
|
96
|
-
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
|
|
97
68
|
const [selectedCategory, setSelectedCategory] = useState<string>('All');
|
|
69
|
+
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
|
|
98
70
|
const [searchQuery, setSearchQuery] = useState('');
|
|
99
71
|
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
prev.map((c) => (c.id === id ? { ...c, isBookmarked: !c.isBookmarked } : c))
|
|
103
|
-
);
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const filteredCards = cards.filter((card) => {
|
|
107
|
-
const matchesCategory = selectedCategory === 'All' || card.category === selectedCategory;
|
|
72
|
+
const filteredCards = cards.filter((c) => {
|
|
73
|
+
const matchesCat = selectedCategory === 'All' || c.category === selectedCategory;
|
|
108
74
|
const matchesSearch =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return matchesCategory && matchesSearch;
|
|
75
|
+
c.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
76
|
+
c.description.toLowerCase().includes(searchQuery.toLowerCase());
|
|
77
|
+
return matchesCat && matchesSearch;
|
|
113
78
|
});
|
|
114
79
|
|
|
115
80
|
const getIcon = (type: CardItem['icon']) => {
|
|
@@ -119,191 +84,131 @@ export function CardListBlock() {
|
|
|
119
84
|
case 'layers':
|
|
120
85
|
return <Layers className="w-4 h-4 text-blue-500" />;
|
|
121
86
|
case 'shield':
|
|
122
|
-
return <
|
|
87
|
+
return <Shield className="w-4 h-4 text-emerald-500" />;
|
|
123
88
|
case 'zap':
|
|
124
89
|
return <Zap className="w-4 h-4 text-amber-500" />;
|
|
125
90
|
}
|
|
126
91
|
};
|
|
127
92
|
|
|
128
93
|
return (
|
|
129
|
-
<
|
|
130
|
-
{/*
|
|
131
|
-
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-
|
|
132
|
-
{/* Category
|
|
133
|
-
<div className="flex
|
|
94
|
+
<Card className="overflow-hidden transition-colors">
|
|
95
|
+
{/* 1. Filter Toolbar composed with Button components */}
|
|
96
|
+
<div className="p-4 border-b border-(--border-subtle) bg-(--bg-secondary)/20 flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
|
97
|
+
{/* Category Pills */}
|
|
98
|
+
<div className="flex items-center gap-1 overflow-x-auto scrollbar-none">
|
|
134
99
|
{['All', 'Foundation', 'Component', 'Pattern'].map((cat) => (
|
|
135
|
-
<
|
|
100
|
+
<Button
|
|
136
101
|
key={cat}
|
|
102
|
+
variant={selectedCategory === cat ? 'primary' : 'ghost'}
|
|
103
|
+
size="sm"
|
|
137
104
|
onClick={() => setSelectedCategory(cat)}
|
|
138
|
-
className=
|
|
139
|
-
selectedCategory === cat
|
|
140
|
-
? 'bg-(--accent-primary) text-white shadow-xs'
|
|
141
|
-
: 'bg-(--bg-secondary) text-(--text-secondary) hover:text-(--text-primary)'
|
|
142
|
-
}`}
|
|
105
|
+
className="text-xs py-1 px-2.5 whitespace-nowrap"
|
|
143
106
|
>
|
|
144
107
|
{cat === 'All' ? '전체' : cat}
|
|
145
|
-
</
|
|
108
|
+
</Button>
|
|
146
109
|
))}
|
|
147
110
|
</div>
|
|
148
111
|
|
|
149
|
-
{/*
|
|
112
|
+
{/* View mode & Search */}
|
|
150
113
|
<div className="flex items-center gap-2">
|
|
151
114
|
<div className="relative">
|
|
152
|
-
<Search className="w-3.5 h-3.5 absolute left-
|
|
115
|
+
<Search className="w-3.5 h-3.5 absolute left-2.5 top-1/2 -translate-y-1/2 text-(--text-secondary)" />
|
|
153
116
|
<input
|
|
154
117
|
type="text"
|
|
155
118
|
value={searchQuery}
|
|
156
119
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
157
|
-
placeholder="
|
|
158
|
-
className="text-xs pl-
|
|
120
|
+
placeholder="카드 필터..."
|
|
121
|
+
className="text-xs pl-7 pr-2.5 py-1 rounded-(--radius-sm) bg-(--bg-secondary) text-(--text-primary) border border-(--border-subtle) focus:outline-none"
|
|
159
122
|
/>
|
|
160
123
|
</div>
|
|
161
124
|
|
|
162
|
-
<div className="flex
|
|
125
|
+
<div className="flex rounded-(--radius-sm) border border-(--border-subtle) p-0.5 bg-(--bg-secondary)">
|
|
163
126
|
<button
|
|
164
127
|
onClick={() => setViewMode('grid')}
|
|
165
|
-
aria-label="
|
|
166
|
-
className={`p-1
|
|
167
|
-
viewMode === 'grid'
|
|
168
|
-
? 'bg-(--bg-primary) text-(--text-primary) shadow-xs'
|
|
169
|
-
: 'text-(--text-muted) hover:text-(--text-primary)'
|
|
128
|
+
aria-label="Grid"
|
|
129
|
+
className={`p-1 rounded-(--radius-sm) transition-colors ${
|
|
130
|
+
viewMode === 'grid' ? 'bg-(--bg-primary) text-(--text-primary) shadow-xs' : 'text-(--text-secondary)'
|
|
170
131
|
}`}
|
|
171
132
|
>
|
|
172
|
-
<LayoutGrid className="w-
|
|
133
|
+
<LayoutGrid className="w-3.5 h-3.5" />
|
|
173
134
|
</button>
|
|
174
135
|
<button
|
|
175
136
|
onClick={() => setViewMode('list')}
|
|
176
|
-
aria-label="
|
|
177
|
-
className={`p-1
|
|
178
|
-
viewMode === 'list'
|
|
179
|
-
? 'bg-(--bg-primary) text-(--text-primary) shadow-xs'
|
|
180
|
-
: 'text-(--text-muted) hover:text-(--text-primary)'
|
|
137
|
+
aria-label="List"
|
|
138
|
+
className={`p-1 rounded-(--radius-sm) transition-colors ${
|
|
139
|
+
viewMode === 'list' ? 'bg-(--bg-primary) text-(--text-primary) shadow-xs' : 'text-(--text-secondary)'
|
|
181
140
|
}`}
|
|
182
141
|
>
|
|
183
|
-
<ListIcon className="w-
|
|
142
|
+
<ListIcon className="w-3.5 h-3.5" />
|
|
184
143
|
</button>
|
|
185
144
|
</div>
|
|
186
145
|
</div>
|
|
187
146
|
</div>
|
|
188
147
|
|
|
189
|
-
{/* Cards Viewport */}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
key={card.id}
|
|
200
|
-
className="group flex flex-col justify-between p-4 rounded-xl bg-(--bg-secondary)/50 hover:bg-(--bg-secondary) border border-(--border-subtle) hover:border-(--border-strong) transition-all duration-200 hover:-translate-y-0.5 shadow-xs"
|
|
201
|
-
>
|
|
202
|
-
<div>
|
|
203
|
-
<div className="flex items-center justify-between mb-3">
|
|
204
|
-
<div className="flex items-center gap-2">
|
|
205
|
-
<div className="p-2 rounded-lg bg-(--bg-primary) border border-(--border-subtle)">
|
|
206
|
-
{getIcon(card.icon)}
|
|
207
|
-
</div>
|
|
208
|
-
<span className="text-[11px] font-semibold px-2 py-0.5 rounded-full bg-(--bg-primary) text-(--text-secondary) border border-(--border-subtle)">
|
|
209
|
-
{card.tag}
|
|
210
|
-
</span>
|
|
211
|
-
</div>
|
|
212
|
-
<button
|
|
213
|
-
onClick={() => toggleBookmark(card.id)}
|
|
214
|
-
className="p-1.5 rounded-lg text-(--text-muted) hover:text-(--accent-primary) transition-colors"
|
|
215
|
-
>
|
|
216
|
-
<Bookmark
|
|
217
|
-
className={`w-4 h-4 ${card.isBookmarked ? 'fill-(--accent-primary) text-(--accent-primary)' : ''}`}
|
|
218
|
-
/>
|
|
219
|
-
</button>
|
|
220
|
-
</div>
|
|
221
|
-
|
|
222
|
-
<h4 className="text-sm font-bold text-(--text-primary) group-hover:text-(--accent-primary) transition-colors line-clamp-1 mb-1.5">
|
|
223
|
-
{card.title}
|
|
224
|
-
</h4>
|
|
225
|
-
<p className="text-xs text-(--text-secondary) leading-relaxed line-clamp-2 mb-4">
|
|
226
|
-
{card.description}
|
|
227
|
-
</p>
|
|
228
|
-
</div>
|
|
229
|
-
|
|
230
|
-
<div className="flex items-center justify-between pt-3 border-t border-(--border-subtle) text-[11px] text-(--text-muted)">
|
|
231
|
-
<div className="flex items-center gap-3">
|
|
232
|
-
<span className="flex items-center gap-1">
|
|
233
|
-
<Star className="w-3.5 h-3.5 fill-amber-400 text-amber-400" />
|
|
234
|
-
<span className="font-semibold text-(--text-primary)">{card.stats.rating}</span>
|
|
235
|
-
</span>
|
|
236
|
-
<span className="flex items-center gap-1">
|
|
237
|
-
<Eye className="w-3.5 h-3.5" />
|
|
238
|
-
<span>{card.stats.views}</span>
|
|
239
|
-
</span>
|
|
240
|
-
</div>
|
|
241
|
-
<div className="flex items-center gap-1 font-semibold text-(--accent-primary) group-hover:translate-x-0.5 transition-transform">
|
|
242
|
-
<span>보기</span>
|
|
243
|
-
<ArrowUpRight className="w-3 h-3" />
|
|
244
|
-
</div>
|
|
245
|
-
</div>
|
|
246
|
-
</div>
|
|
247
|
-
))}
|
|
248
|
-
</div>
|
|
249
|
-
) : (
|
|
250
|
-
/* List Layout */
|
|
251
|
-
<div className="space-y-3 pt-5">
|
|
252
|
-
{filteredCards.map((card) => (
|
|
253
|
-
<div
|
|
254
|
-
key={card.id}
|
|
255
|
-
className="group flex flex-col sm:flex-row sm:items-center justify-between p-4 rounded-xl bg-(--bg-secondary)/50 hover:bg-(--bg-secondary) border border-(--border-subtle) hover:border-(--border-strong) transition-all gap-4 shadow-xs"
|
|
256
|
-
>
|
|
257
|
-
<div className="flex items-start sm:items-center gap-3">
|
|
258
|
-
<div className="p-2.5 rounded-lg bg-(--bg-primary) border border-(--border-subtle) shrink-0 mt-0.5 sm:mt-0">
|
|
259
|
-
{getIcon(card.icon)}
|
|
260
|
-
</div>
|
|
148
|
+
{/* 2. Cards Viewport composed with UI Card & Badge */}
|
|
149
|
+
<div className="p-4 sm:p-5">
|
|
150
|
+
{viewMode === 'grid' ? (
|
|
151
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3.5">
|
|
152
|
+
{filteredCards.map((c) => (
|
|
153
|
+
<Card
|
|
154
|
+
key={c.id}
|
|
155
|
+
interactive
|
|
156
|
+
className="p-4 flex flex-col justify-between"
|
|
157
|
+
>
|
|
261
158
|
<div>
|
|
262
|
-
<div className="flex items-center
|
|
263
|
-
<
|
|
264
|
-
{
|
|
265
|
-
</
|
|
266
|
-
<
|
|
159
|
+
<div className="flex items-center justify-between mb-2.5">
|
|
160
|
+
<div className="p-2 rounded-(--radius-sm) bg-(--bg-secondary) border border-(--border-subtle)">
|
|
161
|
+
{getIcon(c.icon)}
|
|
162
|
+
</div>
|
|
163
|
+
<Badge variant="neutral" className="text-[11px]">
|
|
164
|
+
{c.tag}
|
|
165
|
+
</Badge>
|
|
267
166
|
</div>
|
|
268
|
-
<h4 className="
|
|
269
|
-
{
|
|
167
|
+
<h4 className="font-semibold text-xs text-(--text-primary) line-clamp-1 mb-1">
|
|
168
|
+
{c.title}
|
|
270
169
|
</h4>
|
|
271
|
-
<p className="text-xs text-(--text-secondary)
|
|
272
|
-
{
|
|
170
|
+
<p className="text-xs text-(--text-secondary) line-clamp-2 leading-relaxed">
|
|
171
|
+
{c.description}
|
|
273
172
|
</p>
|
|
274
173
|
</div>
|
|
275
|
-
</div>
|
|
276
174
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
<
|
|
280
|
-
<
|
|
281
|
-
<span className="font-semibold text-(--text-primary)">{card.stats.rating}</span>
|
|
282
|
-
</span>
|
|
283
|
-
<span className="flex items-center gap-1">
|
|
284
|
-
<Eye className="w-3.5 h-3.5" />
|
|
285
|
-
<span>{card.stats.views}</span>
|
|
286
|
-
</span>
|
|
287
|
-
</div>
|
|
288
|
-
<div className="flex items-center gap-2">
|
|
289
|
-
<button
|
|
290
|
-
onClick={() => toggleBookmark(card.id)}
|
|
291
|
-
className="p-1.5 rounded-lg text-(--text-muted) hover:text-(--accent-primary) transition-colors"
|
|
292
|
-
>
|
|
293
|
-
<Bookmark
|
|
294
|
-
className={`w-4 h-4 ${card.isBookmarked ? 'fill-(--accent-primary) text-(--accent-primary)' : ''}`}
|
|
295
|
-
/>
|
|
296
|
-
</button>
|
|
297
|
-
<button className="px-3 py-1.5 rounded-lg text-xs font-semibold bg-(--bg-primary) hover:bg-(--accent-primary) hover:text-white text-(--text-primary) border border-(--border-subtle) transition-colors flex items-center gap-1">
|
|
298
|
-
<span>상세보기</span>
|
|
175
|
+
<div className="pt-3 border-t border-(--border-subtle) flex items-center justify-between mt-3 text-xs">
|
|
176
|
+
<span className="font-semibold text-(--text-primary)">{c.price}</span>
|
|
177
|
+
<button className="text-xs font-medium text-(--accent-primary) hover:underline flex items-center gap-0.5">
|
|
178
|
+
<span>선택</span>
|
|
299
179
|
<ArrowUpRight className="w-3 h-3" />
|
|
300
180
|
</button>
|
|
301
181
|
</div>
|
|
302
|
-
</
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
182
|
+
</Card>
|
|
183
|
+
))}
|
|
184
|
+
</div>
|
|
185
|
+
) : (
|
|
186
|
+
<div className="space-y-2">
|
|
187
|
+
{filteredCards.map((c) => (
|
|
188
|
+
<Card
|
|
189
|
+
key={c.id}
|
|
190
|
+
className="p-3 px-4 flex items-center justify-between transition-colors gap-3"
|
|
191
|
+
>
|
|
192
|
+
<div className="flex items-center gap-3">
|
|
193
|
+
<div className="p-1.5 rounded-(--radius-sm) bg-(--bg-secondary) border border-(--border-subtle)">
|
|
194
|
+
{getIcon(c.icon)}
|
|
195
|
+
</div>
|
|
196
|
+
<div>
|
|
197
|
+
<div className="font-semibold text-xs text-(--text-primary)">{c.title}</div>
|
|
198
|
+
<div className="text-[11px] text-(--text-secondary) line-clamp-1">{c.description}</div>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
<div className="flex items-center gap-3 shrink-0 text-xs">
|
|
202
|
+
<span className="font-semibold text-(--text-primary)">{c.price}</span>
|
|
203
|
+
<Button variant="secondary" size="sm" className="text-xs py-1 px-2.5">
|
|
204
|
+
상세보기
|
|
205
|
+
</Button>
|
|
206
|
+
</div>
|
|
207
|
+
</Card>
|
|
208
|
+
))}
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
</Card>
|
|
308
213
|
);
|
|
309
214
|
}
|