@wakastellar/ui 2.3.4 → 2.4.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/dist/blocks/dashboard/index.d.ts +4 -1
- package/dist/blocks/empty-states/index.d.ts +4 -1
- package/dist/blocks/error-pages/index.d.ts +4 -1
- package/dist/blocks/index.d.ts +1 -1
- package/dist/blocks/landing/index.d.ts +4 -1
- package/dist/blocks/pricing/index.d.ts +5 -1
- package/dist/blocks/sidebar/index.d.ts +5 -1
- package/dist/index.cjs.js +130 -130
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +7905 -7856
- package/dist/stories/Button.d.ts +14 -0
- package/dist/stories/Button.stories.d.ts +8 -0
- package/dist/stories/Header.d.ts +11 -0
- package/dist/stories/Header.stories.d.ts +6 -0
- package/dist/stories/Page.d.ts +2 -0
- package/dist/stories/Page.stories.d.ts +6 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/link.d.ts +23 -0
- package/package.json +11 -3
- package/src/blocks/activity-timeline/ActivityTimeline.stories.tsx +460 -0
- package/src/blocks/apm-overview/APMOverview.stories.tsx +435 -0
- package/src/blocks/auth-2fa/Auth2FA.stories.tsx +308 -0
- package/src/blocks/calendar-view/CalendarView.stories.tsx +398 -0
- package/src/blocks/chat/Chat.stories.tsx +466 -0
- package/src/blocks/chat-interface/ChatInterface.stories.tsx +412 -0
- package/src/blocks/checkout-flow/CheckoutFlow.stories.tsx +408 -0
- package/src/blocks/cicd-builder/CICDBuilder.stories.tsx +499 -0
- package/src/blocks/cloud-cost-dashboard/CloudCostDashboard.stories.tsx +356 -0
- package/src/blocks/container-orchestrator/ContainerOrchestrator.stories.tsx +461 -0
- package/src/blocks/dashboard/Dashboard.stories.tsx +559 -0
- package/src/blocks/dashboard/index.tsx +68 -27
- package/src/blocks/dashboard-kpi/DashboardKPI.stories.tsx +422 -0
- package/src/blocks/database-admin/DatabaseAdmin.stories.tsx +393 -0
- package/src/blocks/deployment-dashboard/DeploymentDashboard.stories.tsx +457 -0
- package/src/blocks/empty-states/EmptyStates.stories.tsx +467 -0
- package/src/blocks/empty-states/index.tsx +26 -8
- package/src/blocks/error-pages/ErrorPages.stories.tsx +401 -0
- package/src/blocks/error-pages/index.tsx +26 -8
- package/src/blocks/faq/FAQ.stories.tsx +416 -0
- package/src/blocks/file-manager/FileManager.stories.tsx +413 -0
- package/src/blocks/footer/Footer.stories.tsx +328 -0
- package/src/blocks/gitops-sync-status/GitOpsSyncStatus.stories.tsx +529 -0
- package/src/blocks/header/WakaHeader.stories.tsx +455 -0
- package/src/blocks/headtab/Headtab.stories.tsx +369 -0
- package/src/blocks/i18n-editor/I18nEditor.stories.tsx +451 -0
- package/src/blocks/incident-manager/IncidentManager.stories.tsx +464 -0
- package/src/blocks/index.ts +1 -1
- package/src/blocks/infrastructure-map/InfrastructureMap.stories.tsx +533 -0
- package/src/blocks/kanban-board/KanbanBoard.stories.tsx +494 -0
- package/src/blocks/landing/WakaLanding.stories.tsx +449 -0
- package/src/blocks/landing/index.tsx +125 -66
- package/src/blocks/language-selector/LanguageSelector.stories.tsx +345 -0
- package/src/blocks/layout/Layout.stories.tsx +373 -0
- package/src/blocks/login/Login.stories.tsx +443 -0
- package/src/blocks/on-call-schedule/OnCallSchedule.stories.tsx +381 -0
- package/src/blocks/player-profile/PlayerProfile.stories.tsx +316 -0
- package/src/blocks/pricing/WakaPricing.stories.tsx +530 -0
- package/src/blocks/pricing/index.tsx +38 -4
- package/src/blocks/profile/Profile.stories.tsx +371 -0
- package/src/blocks/release-notes/ReleaseNotes.stories.tsx +431 -0
- package/src/blocks/settings/Settings.stories.tsx +520 -0
- package/src/blocks/sidebar/WakaSidebar.stories.tsx +513 -0
- package/src/blocks/sidebar/index.tsx +49 -20
- package/src/blocks/theme-creator-block/ThemeCreatorBlock.stories.tsx +370 -0
- package/src/blocks/user-management/UserManagement.stories.tsx +411 -0
- package/src/blocks/wizard/Wizard.stories.tsx +683 -0
- package/src/components/accordion/Accordion.stories.tsx +93 -0
- package/src/components/alert/Alert.stories.tsx +95 -0
- package/src/components/alert-dialog/AlertDialog.stories.tsx +126 -0
- package/src/components/aspect-ratio/AspectRatio.stories.tsx +118 -0
- package/src/components/avatar/Avatar.stories.tsx +104 -0
- package/src/components/button/Button.stories.tsx +12 -1
- package/src/components/calendar/Calendar.stories.tsx +102 -0
- package/src/components/card/Card.stories.tsx +125 -0
- package/src/components/checkbox/Checkbox.stories.tsx +100 -0
- package/src/components/code/Code.stories.tsx +402 -0
- package/src/components/collapsible/Collapsible.stories.tsx +123 -0
- package/src/components/command/Command.stories.tsx +207 -0
- package/src/components/context-menu/ContextMenu.stories.tsx +220 -0
- package/src/components/dialog/Dialog.stories.tsx +157 -0
- package/src/components/dropdown-menu/DropdownMenu.stories.tsx +225 -0
- package/src/components/form/Form.stories.tsx +413 -0
- package/src/components/hover-card/HoverCard.stories.tsx +148 -0
- package/src/components/input-otp/InputOTP.stories.tsx +255 -0
- package/src/components/label/Label.stories.tsx +68 -0
- package/src/components/menubar/Menubar.stories.tsx +278 -0
- package/src/components/navigation-menu/NavigationMenu.stories.tsx +202 -0
- package/src/components/popover/Popover.stories.tsx +199 -0
- package/src/components/progress/Progress.stories.tsx +104 -0
- package/src/components/radio-group/RadioGroup.stories.tsx +138 -0
- package/src/components/scroll-area/ScrollArea.stories.tsx +153 -0
- package/src/components/select/Select.stories.tsx +146 -0
- package/src/components/separator/Separator.stories.tsx +117 -0
- package/src/components/sheet/Sheet.stories.tsx +195 -0
- package/src/components/skeleton/Skeleton.stories.tsx +114 -0
- package/src/components/slider/Slider.stories.tsx +157 -0
- package/src/components/switch/Switch.stories.tsx +114 -0
- package/src/components/table/Table.stories.tsx +153 -0
- package/src/components/tabs/Tabs.stories.tsx +155 -0
- package/src/components/textarea/Textarea.stories.tsx +116 -0
- package/src/components/toast/Toast.stories.tsx +160 -0
- package/src/components/toggle/Toggle.stories.tsx +125 -0
- package/src/components/tooltip/Tooltip.stories.tsx +133 -0
- package/src/components/typography/Typography.stories.tsx +207 -0
- package/src/components/waka-3d-pie-chart/Waka3DPieChart.stories.tsx +308 -0
- package/src/components/waka-achievement-unlock/WakaAchievementUnlock.stories.tsx +353 -0
- package/src/components/waka-artifact-list/WakaArtifactList.stories.tsx +258 -0
- package/src/components/waka-autocomplete/WakaAutocomplete.stories.tsx +224 -0
- package/src/components/waka-badge-showcase/WakaBadgeShowcase.stories.tsx +269 -0
- package/src/components/waka-barcode/WakaBarcode.stories.tsx +227 -0
- package/src/components/waka-bottom-sheet/WakaBottomSheet.stories.tsx +408 -0
- package/src/components/waka-breadcrumb/WakaBreadcrumb.stories.tsx +237 -0
- package/src/components/waka-build-matrix/WakaBuildMatrix.stories.tsx +328 -0
- package/src/components/waka-carousel/WakaCarousel.stories.tsx +296 -0
- package/src/components/waka-charts/WakaCharts.stories.tsx +519 -0
- package/src/components/waka-color-picker/WakaColorPicker.stories.tsx +200 -0
- package/src/components/waka-combobox/WakaCombobox.stories.tsx +250 -0
- package/src/components/waka-container-list/WakaContainerList.stories.tsx +315 -0
- package/src/components/waka-contribution-graph/WakaContributionGraph.stories.tsx +354 -0
- package/src/components/waka-cost-breakdown/WakaCostBreakdown.stories.tsx +348 -0
- package/src/components/waka-daily-reward/WakaDailyReward.stories.tsx +365 -0
- package/src/components/waka-database-card/WakaDatabaseCard.stories.tsx +306 -0
- package/src/components/waka-date-range-picker/WakaDateRangePicker.stories.tsx +339 -0
- package/src/components/waka-datetime-picker/WakaDateTimePicker.stories.tsx +317 -0
- package/src/components/waka-deployment-lane/WakaDeploymentLane.stories.tsx +292 -0
- package/src/components/waka-dock/WakaDock.stories.tsx +332 -0
- package/src/components/waka-drawer/WakaDrawer.stories.tsx +437 -0
- package/src/components/waka-env-var-editor/WakaEnvVarEditor.stories.tsx +263 -0
- package/src/components/waka-error-shake/WakaErrorShake.stories.tsx +410 -0
- package/src/components/waka-file-upload/WakaFileUpload.stories.tsx +239 -0
- package/src/components/waka-flow-diagram/WakaFlowDiagram.stories.tsx +365 -0
- package/src/components/waka-funnel-chart/WakaFunnelChart.stories.tsx +281 -0
- package/src/components/waka-glow-card/WakaGlowCard.stories.tsx +274 -0
- package/src/components/waka-haptic-button/WakaHapticButton.stories.tsx +349 -0
- package/src/components/waka-health-pulse/WakaHealthPulse.stories.tsx +293 -0
- package/src/components/waka-heatmap/WakaHeatmap.stories.tsx +376 -0
- package/src/components/waka-image/WakaImage.stories.tsx +255 -0
- package/src/components/waka-incident-timeline/WakaIncidentTimeline.stories.tsx +300 -0
- package/src/components/waka-kanban/WakaKanban.stories.tsx +399 -0
- package/src/components/waka-kubernetes-overview/WakaKubernetesOverview.stories.tsx +281 -0
- package/src/components/waka-leaderboard/WakaLeaderboard.stories.tsx +300 -0
- package/src/components/waka-level-progress/WakaLevelProgress.stories.tsx +313 -0
- package/src/components/waka-loading-orbit/WakaLoadingOrbit.stories.tsx +413 -0
- package/src/components/waka-log-viewer/WakaLogViewer.stories.tsx +312 -0
- package/src/components/waka-loot-box/WakaLootBox.stories.tsx +374 -0
- package/src/components/waka-metric-sparkline/WakaMetricSparkline.stories.tsx +312 -0
- package/src/components/waka-migration-list/WakaMigrationList.stories.tsx +289 -0
- package/src/components/waka-modal/WakaModal.stories.tsx +434 -0
- package/src/components/waka-morph-button/WakaMorphButton.stories.tsx +405 -0
- package/src/components/waka-network-topology/WakaNetworkTopology.stories.tsx +364 -0
- package/src/components/waka-notifications/WakaNotifications.stories.tsx +290 -0
- package/src/components/waka-number-input/WakaNumberInput.stories.tsx +282 -0
- package/src/components/waka-pagination/WakaPagination.stories.tsx +328 -0
- package/src/components/waka-password-strength/WakaPasswordStrength.stories.tsx +318 -0
- package/src/components/waka-pipeline-view/WakaPipelineView.stories.tsx +386 -0
- package/src/components/waka-player-card/WakaPlayerCard.stories.tsx +333 -0
- package/src/components/waka-pod-card/WakaPodCard.stories.tsx +435 -0
- package/src/components/waka-qrcode/WakaQRCode.stories.tsx +232 -0
- package/src/components/waka-query-explain/WakaQueryExplain.stories.tsx +407 -0
- package/src/components/waka-quest-card/WakaQuestCard.stories.tsx +394 -0
- package/src/components/waka-quota-bar/WakaQuotaBar.stories.tsx +435 -0
- package/src/components/waka-radar-score/WakaRadarScore.stories.tsx +372 -0
- package/src/components/waka-resource-gauge/WakaResourceGauge.stories.tsx +366 -0
- package/src/components/waka-rich-text-editor/WakaRichTextEditor.stories.tsx +238 -0
- package/src/components/waka-sankey-diagram/WakaSankeyDiagram.stories.tsx +389 -0
- package/src/components/waka-scratch-card/WakaScratchCard.stories.tsx +388 -0
- package/src/components/waka-secret-card/WakaSecretCard.stories.tsx +314 -0
- package/src/components/waka-segmented-control/WakaSegmentedControl.stories.tsx +309 -0
- package/src/components/waka-server-rack/WakaServerRack.stories.tsx +382 -0
- package/src/components/waka-service-graph/WakaServiceGraph.stories.tsx +262 -0
- package/src/components/waka-skeleton-wave/WakaSkeletonWave.stories.tsx +321 -0
- package/src/components/waka-skill-tree/WakaSkillTree.stories.tsx +308 -0
- package/src/components/waka-spin-wheel/WakaSpinWheel.stories.tsx +368 -0
- package/src/components/waka-spinner/WakaSpinner.stories.tsx +156 -0
- package/src/components/waka-stat/WakaStat.stories.tsx +334 -0
- package/src/components/waka-status-matrix/WakaStatusMatrix.stories.tsx +331 -0
- package/src/components/waka-stepper/WakaStepper.stories.tsx +468 -0
- package/src/components/waka-streak-counter/WakaStreakCounter.stories.tsx +235 -0
- package/src/components/waka-success-explosion/WakaSuccessExplosion.stories.tsx +389 -0
- package/src/components/waka-tabs-morph/WakaTabsMorph.stories.tsx +471 -0
- package/src/components/waka-terminal-output/WakaTerminalOutput.stories.tsx +351 -0
- package/src/components/waka-test-report/WakaTestReport.stories.tsx +322 -0
- package/src/components/waka-tilt-card/WakaTiltCard.stories.tsx +300 -0
- package/src/components/waka-time-picker/WakaTimePicker.stories.tsx +227 -0
- package/src/components/waka-timeline/WakaTimeline.stories.tsx +383 -0
- package/src/components/waka-tournament-bracket/WakaTournamentBracket.stories.tsx +375 -0
- package/src/components/waka-trace-viewer/WakaTraceViewer.stories.tsx +445 -0
- package/src/components/waka-tree/WakaTree.stories.tsx +359 -0
- package/src/components/waka-treemap-chart/WakaTreemapChart.stories.tsx +378 -0
- package/src/components/waka-typewriter/WakaTypewriter.stories.tsx +366 -0
- package/src/components/waka-versus-card/WakaVersusCard.stories.tsx +530 -0
- package/src/components/waka-video/WakaVideo.stories.tsx +203 -0
- package/src/components/waka-virtual-list/WakaVirtualList.stories.tsx +273 -0
- package/src/components/waka-xp-bar/WakaXPBar.stories.tsx +305 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { WakaSegmentedControl, useSegmentedControl } from './index'
|
|
3
|
+
import type { SegmentOption } from './index'
|
|
4
|
+
import * as React from 'react'
|
|
5
|
+
import { List, Grid, LayoutGrid, Sun, Moon, Monitor, AlignLeft, AlignCenter, AlignRight, AlignJustify } from 'lucide-react'
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof WakaSegmentedControl> = {
|
|
8
|
+
title: 'Components/Display/WakaSegmentedControl',
|
|
9
|
+
component: WakaSegmentedControl,
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'centered',
|
|
12
|
+
docs: {
|
|
13
|
+
description: {
|
|
14
|
+
component: 'A segmented control component for switching between related options with an animated indicator.',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
tags: ['autodocs'],
|
|
19
|
+
argTypes: {
|
|
20
|
+
size: {
|
|
21
|
+
control: 'select',
|
|
22
|
+
options: ['sm', 'md', 'lg'],
|
|
23
|
+
description: 'Size of the control',
|
|
24
|
+
},
|
|
25
|
+
variant: {
|
|
26
|
+
control: 'select',
|
|
27
|
+
options: ['default', 'pills'],
|
|
28
|
+
description: 'Visual variant',
|
|
29
|
+
},
|
|
30
|
+
fullWidth: {
|
|
31
|
+
control: 'boolean',
|
|
32
|
+
description: 'Take full width of container',
|
|
33
|
+
},
|
|
34
|
+
disabled: {
|
|
35
|
+
control: 'boolean',
|
|
36
|
+
description: 'Disable the control',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default meta
|
|
42
|
+
type Story = StoryObj<typeof WakaSegmentedControl>
|
|
43
|
+
|
|
44
|
+
const viewOptions: SegmentOption[] = [
|
|
45
|
+
{ value: 'list', label: 'List' },
|
|
46
|
+
{ value: 'grid', label: 'Grid' },
|
|
47
|
+
{ value: 'gallery', label: 'Gallery' },
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
export const Default: Story = {
|
|
51
|
+
args: {
|
|
52
|
+
options: viewOptions,
|
|
53
|
+
},
|
|
54
|
+
render: (args) => {
|
|
55
|
+
const [value, setValue] = React.useState('list')
|
|
56
|
+
return (
|
|
57
|
+
<WakaSegmentedControl
|
|
58
|
+
{...args}
|
|
59
|
+
value={value}
|
|
60
|
+
onChange={setValue}
|
|
61
|
+
/>
|
|
62
|
+
)
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const Sizes: Story = {
|
|
67
|
+
render: () => {
|
|
68
|
+
const [value, setValue] = React.useState('list')
|
|
69
|
+
return (
|
|
70
|
+
<div className="space-y-6">
|
|
71
|
+
<div>
|
|
72
|
+
<p className="text-sm text-muted-foreground mb-2">Small</p>
|
|
73
|
+
<WakaSegmentedControl
|
|
74
|
+
options={viewOptions}
|
|
75
|
+
value={value}
|
|
76
|
+
onChange={setValue}
|
|
77
|
+
size="sm"
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
<div>
|
|
81
|
+
<p className="text-sm text-muted-foreground mb-2">Medium (Default)</p>
|
|
82
|
+
<WakaSegmentedControl
|
|
83
|
+
options={viewOptions}
|
|
84
|
+
value={value}
|
|
85
|
+
onChange={setValue}
|
|
86
|
+
size="md"
|
|
87
|
+
/>
|
|
88
|
+
</div>
|
|
89
|
+
<div>
|
|
90
|
+
<p className="text-sm text-muted-foreground mb-2">Large</p>
|
|
91
|
+
<WakaSegmentedControl
|
|
92
|
+
options={viewOptions}
|
|
93
|
+
value={value}
|
|
94
|
+
onChange={setValue}
|
|
95
|
+
size="lg"
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
)
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const Variants: Story = {
|
|
104
|
+
render: () => {
|
|
105
|
+
const [value, setValue] = React.useState('list')
|
|
106
|
+
return (
|
|
107
|
+
<div className="space-y-6">
|
|
108
|
+
<div>
|
|
109
|
+
<p className="text-sm text-muted-foreground mb-2">Default (Animated Indicator)</p>
|
|
110
|
+
<WakaSegmentedControl
|
|
111
|
+
options={viewOptions}
|
|
112
|
+
value={value}
|
|
113
|
+
onChange={setValue}
|
|
114
|
+
variant="default"
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
117
|
+
<div>
|
|
118
|
+
<p className="text-sm text-muted-foreground mb-2">Pills</p>
|
|
119
|
+
<WakaSegmentedControl
|
|
120
|
+
options={viewOptions}
|
|
121
|
+
value={value}
|
|
122
|
+
onChange={setValue}
|
|
123
|
+
variant="pills"
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
)
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const WithIcons: Story = {
|
|
132
|
+
render: () => {
|
|
133
|
+
const optionsWithIcons: SegmentOption[] = [
|
|
134
|
+
{ value: 'list', label: 'List', icon: <List className="h-4 w-4" /> },
|
|
135
|
+
{ value: 'grid', label: 'Grid', icon: <Grid className="h-4 w-4" /> },
|
|
136
|
+
{ value: 'gallery', label: 'Gallery', icon: <LayoutGrid className="h-4 w-4" /> },
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
const [value, setValue] = React.useState('list')
|
|
140
|
+
return (
|
|
141
|
+
<WakaSegmentedControl
|
|
142
|
+
options={optionsWithIcons}
|
|
143
|
+
value={value}
|
|
144
|
+
onChange={setValue}
|
|
145
|
+
/>
|
|
146
|
+
)
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const IconOnly: Story = {
|
|
151
|
+
render: () => {
|
|
152
|
+
const iconOnlyOptions: SegmentOption[] = [
|
|
153
|
+
{ value: 'left', label: '', icon: <AlignLeft className="h-4 w-4" /> },
|
|
154
|
+
{ value: 'center', label: '', icon: <AlignCenter className="h-4 w-4" /> },
|
|
155
|
+
{ value: 'right', label: '', icon: <AlignRight className="h-4 w-4" /> },
|
|
156
|
+
{ value: 'justify', label: '', icon: <AlignJustify className="h-4 w-4" /> },
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
const [value, setValue] = React.useState('left')
|
|
160
|
+
return (
|
|
161
|
+
<WakaSegmentedControl
|
|
162
|
+
options={iconOnlyOptions}
|
|
163
|
+
value={value}
|
|
164
|
+
onChange={setValue}
|
|
165
|
+
/>
|
|
166
|
+
)
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const ThemeSwitcher: Story = {
|
|
171
|
+
render: () => {
|
|
172
|
+
const themeOptions: SegmentOption[] = [
|
|
173
|
+
{ value: 'light', label: 'Light', icon: <Sun className="h-4 w-4" /> },
|
|
174
|
+
{ value: 'dark', label: 'Dark', icon: <Moon className="h-4 w-4" /> },
|
|
175
|
+
{ value: 'system', label: 'System', icon: <Monitor className="h-4 w-4" /> },
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
const [value, setValue] = React.useState('system')
|
|
179
|
+
return (
|
|
180
|
+
<div className="border rounded-lg p-6 w-[400px]">
|
|
181
|
+
<h3 className="font-semibold mb-4">Appearance</h3>
|
|
182
|
+
<WakaSegmentedControl
|
|
183
|
+
options={themeOptions}
|
|
184
|
+
value={value}
|
|
185
|
+
onChange={setValue}
|
|
186
|
+
fullWidth
|
|
187
|
+
/>
|
|
188
|
+
<p className="text-sm text-muted-foreground mt-4">
|
|
189
|
+
Current theme: <span className="font-medium capitalize">{value}</span>
|
|
190
|
+
</p>
|
|
191
|
+
</div>
|
|
192
|
+
)
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export const FullWidth: Story = {
|
|
197
|
+
render: () => {
|
|
198
|
+
const [value, setValue] = React.useState('list')
|
|
199
|
+
return (
|
|
200
|
+
<div className="w-[400px]">
|
|
201
|
+
<WakaSegmentedControl
|
|
202
|
+
options={viewOptions}
|
|
203
|
+
value={value}
|
|
204
|
+
onChange={setValue}
|
|
205
|
+
fullWidth
|
|
206
|
+
/>
|
|
207
|
+
</div>
|
|
208
|
+
)
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export const WithDisabledOptions: Story = {
|
|
213
|
+
render: () => {
|
|
214
|
+
const optionsWithDisabled: SegmentOption[] = [
|
|
215
|
+
{ value: 'free', label: 'Free' },
|
|
216
|
+
{ value: 'pro', label: 'Pro' },
|
|
217
|
+
{ value: 'enterprise', label: 'Enterprise', disabled: true },
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
const [value, setValue] = React.useState('free')
|
|
221
|
+
return (
|
|
222
|
+
<WakaSegmentedControl
|
|
223
|
+
options={optionsWithDisabled}
|
|
224
|
+
value={value}
|
|
225
|
+
onChange={setValue}
|
|
226
|
+
/>
|
|
227
|
+
)
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export const Disabled: Story = {
|
|
232
|
+
render: () => {
|
|
233
|
+
const [value, setValue] = React.useState('list')
|
|
234
|
+
return (
|
|
235
|
+
<WakaSegmentedControl
|
|
236
|
+
options={viewOptions}
|
|
237
|
+
value={value}
|
|
238
|
+
onChange={setValue}
|
|
239
|
+
disabled
|
|
240
|
+
/>
|
|
241
|
+
)
|
|
242
|
+
},
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export const WithHook: Story = {
|
|
246
|
+
render: () => {
|
|
247
|
+
const { value, onChange, selectNext, selectPrevious } = useSegmentedControl(viewOptions, 'list')
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<div className="space-y-4 w-[400px]">
|
|
251
|
+
<WakaSegmentedControl
|
|
252
|
+
options={viewOptions}
|
|
253
|
+
value={value}
|
|
254
|
+
onChange={onChange}
|
|
255
|
+
/>
|
|
256
|
+
<div className="flex gap-2">
|
|
257
|
+
<button
|
|
258
|
+
onClick={selectPrevious}
|
|
259
|
+
className="px-3 py-1 text-sm border rounded hover:bg-muted"
|
|
260
|
+
>
|
|
261
|
+
Previous
|
|
262
|
+
</button>
|
|
263
|
+
<button
|
|
264
|
+
onClick={selectNext}
|
|
265
|
+
className="px-3 py-1 text-sm border rounded hover:bg-muted"
|
|
266
|
+
>
|
|
267
|
+
Next
|
|
268
|
+
</button>
|
|
269
|
+
</div>
|
|
270
|
+
<p className="text-sm text-muted-foreground">
|
|
271
|
+
Selected: <span className="font-medium capitalize">{value}</span>
|
|
272
|
+
</p>
|
|
273
|
+
</div>
|
|
274
|
+
)
|
|
275
|
+
},
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export const PricingToggle: Story = {
|
|
279
|
+
render: () => {
|
|
280
|
+
const billingOptions: SegmentOption[] = [
|
|
281
|
+
{ value: 'monthly', label: 'Monthly' },
|
|
282
|
+
{ value: 'yearly', label: 'Yearly (Save 20%)' },
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
const [billing, setBilling] = React.useState('monthly')
|
|
286
|
+
const price = billing === 'monthly' ? 29 : 23
|
|
287
|
+
|
|
288
|
+
return (
|
|
289
|
+
<div className="text-center space-y-6 w-[400px]">
|
|
290
|
+
<WakaSegmentedControl
|
|
291
|
+
options={billingOptions}
|
|
292
|
+
value={billing}
|
|
293
|
+
onChange={setBilling}
|
|
294
|
+
variant="pills"
|
|
295
|
+
/>
|
|
296
|
+
<div className="border rounded-lg p-6">
|
|
297
|
+
<h3 className="font-semibold text-lg">Pro Plan</h3>
|
|
298
|
+
<div className="mt-4">
|
|
299
|
+
<span className="text-4xl font-bold">${price}</span>
|
|
300
|
+
<span className="text-muted-foreground">/month</span>
|
|
301
|
+
</div>
|
|
302
|
+
{billing === 'yearly' && (
|
|
303
|
+
<p className="text-sm text-green-600 mt-2">You save $72/year!</p>
|
|
304
|
+
)}
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
)
|
|
308
|
+
},
|
|
309
|
+
}
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { WakaServerRack, WakaServerRackDemo } from './index'
|
|
3
|
+
import type { ServerUnit } from './index'
|
|
4
|
+
import * as React from 'react'
|
|
5
|
+
|
|
6
|
+
const sampleServers: ServerUnit[] = [
|
|
7
|
+
{
|
|
8
|
+
id: 'srv-001',
|
|
9
|
+
name: 'Web Server 01',
|
|
10
|
+
unit: 40,
|
|
11
|
+
height: 2,
|
|
12
|
+
status: 'online',
|
|
13
|
+
cpu: 45,
|
|
14
|
+
ram: 62,
|
|
15
|
+
disk: 34,
|
|
16
|
+
network: { in: 850, out: 420 },
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'srv-002',
|
|
20
|
+
name: 'Database Primary',
|
|
21
|
+
unit: 37,
|
|
22
|
+
height: 3,
|
|
23
|
+
status: 'online',
|
|
24
|
+
cpu: 78,
|
|
25
|
+
ram: 85,
|
|
26
|
+
disk: 67,
|
|
27
|
+
network: { in: 1200, out: 980 },
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'srv-003',
|
|
31
|
+
name: 'Cache Server',
|
|
32
|
+
unit: 35,
|
|
33
|
+
height: 2,
|
|
34
|
+
status: 'warning',
|
|
35
|
+
cpu: 92,
|
|
36
|
+
ram: 88,
|
|
37
|
+
disk: 45,
|
|
38
|
+
network: { in: 2100, out: 1850 },
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'srv-004',
|
|
42
|
+
name: 'API Gateway',
|
|
43
|
+
unit: 33,
|
|
44
|
+
height: 1,
|
|
45
|
+
status: 'online',
|
|
46
|
+
cpu: 34,
|
|
47
|
+
ram: 41,
|
|
48
|
+
disk: 22,
|
|
49
|
+
network: { in: 3500, out: 3200 },
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'srv-005',
|
|
53
|
+
name: 'Worker Node 01',
|
|
54
|
+
unit: 31,
|
|
55
|
+
height: 2,
|
|
56
|
+
status: 'online',
|
|
57
|
+
cpu: 56,
|
|
58
|
+
ram: 67,
|
|
59
|
+
disk: 41,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: 'srv-006',
|
|
63
|
+
name: 'Worker Node 02',
|
|
64
|
+
unit: 29,
|
|
65
|
+
height: 2,
|
|
66
|
+
status: 'offline',
|
|
67
|
+
cpu: 0,
|
|
68
|
+
ram: 0,
|
|
69
|
+
disk: 41,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'srv-007',
|
|
73
|
+
name: 'Storage Array',
|
|
74
|
+
unit: 25,
|
|
75
|
+
height: 4,
|
|
76
|
+
status: 'online',
|
|
77
|
+
cpu: 12,
|
|
78
|
+
ram: 24,
|
|
79
|
+
disk: 89,
|
|
80
|
+
network: { in: 500, out: 4500 },
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: 'srv-008',
|
|
84
|
+
name: 'Backup Server',
|
|
85
|
+
unit: 22,
|
|
86
|
+
height: 3,
|
|
87
|
+
status: 'maintenance',
|
|
88
|
+
cpu: 5,
|
|
89
|
+
ram: 15,
|
|
90
|
+
disk: 72,
|
|
91
|
+
},
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
const meta: Meta<typeof WakaServerRack> = {
|
|
95
|
+
title: 'Components/DevOps/WakaServerRack',
|
|
96
|
+
component: WakaServerRack,
|
|
97
|
+
parameters: {
|
|
98
|
+
layout: 'centered',
|
|
99
|
+
docs: {
|
|
100
|
+
description: {
|
|
101
|
+
component: 'A 3D server rack visualization with status LEDs, CPU/RAM/Disk metrics, hover tooltips, and selection state.',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
backgrounds: { default: 'dark' },
|
|
105
|
+
},
|
|
106
|
+
tags: ['autodocs'],
|
|
107
|
+
argTypes: {
|
|
108
|
+
totalUnits: {
|
|
109
|
+
control: { type: 'number', min: 10, max: 48, step: 1 },
|
|
110
|
+
description: 'Total rack units available',
|
|
111
|
+
},
|
|
112
|
+
showMetrics: {
|
|
113
|
+
control: 'boolean',
|
|
114
|
+
description: 'Show CPU/RAM/Disk metrics bars',
|
|
115
|
+
},
|
|
116
|
+
animated: {
|
|
117
|
+
control: 'boolean',
|
|
118
|
+
description: 'Enable animations',
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export default meta
|
|
124
|
+
type Story = StoryObj<typeof WakaServerRack>
|
|
125
|
+
|
|
126
|
+
export const Default: Story = {
|
|
127
|
+
args: {
|
|
128
|
+
servers: sampleServers,
|
|
129
|
+
totalUnits: 42,
|
|
130
|
+
showMetrics: true,
|
|
131
|
+
animated: true,
|
|
132
|
+
},
|
|
133
|
+
render: (args) => (
|
|
134
|
+
<div className="p-8 bg-zinc-950 rounded-2xl">
|
|
135
|
+
<WakaServerRack {...args} />
|
|
136
|
+
</div>
|
|
137
|
+
),
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export const Demo: Story = {
|
|
141
|
+
render: () => <WakaServerRackDemo />,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export const WithSelection: Story = {
|
|
145
|
+
render: () => {
|
|
146
|
+
const [selected, setSelected] = React.useState<string | null>('srv-002')
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<div className="p-8 bg-zinc-950 rounded-2xl">
|
|
150
|
+
<div className="mb-4 text-zinc-300 text-sm">
|
|
151
|
+
Selected: {selected || 'None'}
|
|
152
|
+
</div>
|
|
153
|
+
<WakaServerRack
|
|
154
|
+
servers={sampleServers}
|
|
155
|
+
totalUnits={42}
|
|
156
|
+
selectedServer={selected || undefined}
|
|
157
|
+
onServerClick={(id) => setSelected(prev => prev === id ? null : id)}
|
|
158
|
+
showMetrics
|
|
159
|
+
animated
|
|
160
|
+
/>
|
|
161
|
+
</div>
|
|
162
|
+
)
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const StatusStates: Story = {
|
|
167
|
+
render: () => {
|
|
168
|
+
const statusServers: ServerUnit[] = [
|
|
169
|
+
{ id: 's1', name: 'Online Server', unit: 10, height: 2, status: 'online', cpu: 45, ram: 60, disk: 30 },
|
|
170
|
+
{ id: 's2', name: 'Warning Server', unit: 8, height: 2, status: 'warning', cpu: 92, ram: 88, disk: 75 },
|
|
171
|
+
{ id: 's3', name: 'Offline Server', unit: 6, height: 2, status: 'offline', cpu: 0, ram: 0, disk: 0 },
|
|
172
|
+
{ id: 's4', name: 'Maintenance', unit: 4, height: 2, status: 'maintenance', cpu: 5, ram: 10, disk: 20 },
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<div className="p-8 bg-zinc-950 rounded-2xl">
|
|
177
|
+
<h3 className="text-zinc-100 font-semibold mb-4">Server Status States</h3>
|
|
178
|
+
<WakaServerRack
|
|
179
|
+
servers={statusServers}
|
|
180
|
+
totalUnits={12}
|
|
181
|
+
showMetrics
|
|
182
|
+
animated
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
)
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export const HighDensity: Story = {
|
|
190
|
+
render: () => {
|
|
191
|
+
const denseServers: ServerUnit[] = Array.from({ length: 20 }, (_, i) => ({
|
|
192
|
+
id: `srv-${i + 1}`,
|
|
193
|
+
name: `Server ${String(i + 1).padStart(2, '0')}`,
|
|
194
|
+
unit: i * 2 + 1,
|
|
195
|
+
height: 1,
|
|
196
|
+
status: ['online', 'online', 'online', 'warning', 'online'][Math.floor(Math.random() * 5)] as ServerUnit['status'],
|
|
197
|
+
cpu: Math.floor(Math.random() * 80) + 10,
|
|
198
|
+
ram: Math.floor(Math.random() * 70) + 20,
|
|
199
|
+
disk: Math.floor(Math.random() * 60) + 10,
|
|
200
|
+
}))
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<div className="p-8 bg-zinc-950 rounded-2xl">
|
|
204
|
+
<h3 className="text-zinc-100 font-semibold mb-4">High Density 1U Servers</h3>
|
|
205
|
+
<WakaServerRack
|
|
206
|
+
servers={denseServers}
|
|
207
|
+
totalUnits={42}
|
|
208
|
+
showMetrics={false}
|
|
209
|
+
animated
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
212
|
+
)
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export const SmallRack: Story = {
|
|
217
|
+
render: () => {
|
|
218
|
+
const smallServers: ServerUnit[] = [
|
|
219
|
+
{ id: 's1', name: 'App Server', unit: 8, height: 2, status: 'online', cpu: 55, ram: 72, disk: 45 },
|
|
220
|
+
{ id: 's2', name: 'DB Server', unit: 5, height: 3, status: 'online', cpu: 68, ram: 81, disk: 62 },
|
|
221
|
+
{ id: 's3', name: 'NAS', unit: 1, height: 4, status: 'online', cpu: 15, ram: 30, disk: 88 },
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<div className="p-8 bg-zinc-950 rounded-2xl">
|
|
226
|
+
<h3 className="text-zinc-100 font-semibold mb-4">12U Cabinet</h3>
|
|
227
|
+
<WakaServerRack
|
|
228
|
+
servers={smallServers}
|
|
229
|
+
totalUnits={12}
|
|
230
|
+
showMetrics
|
|
231
|
+
animated
|
|
232
|
+
/>
|
|
233
|
+
</div>
|
|
234
|
+
)
|
|
235
|
+
},
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export const NoMetrics: Story = {
|
|
239
|
+
render: () => (
|
|
240
|
+
<div className="p-8 bg-zinc-950 rounded-2xl">
|
|
241
|
+
<WakaServerRack
|
|
242
|
+
servers={sampleServers}
|
|
243
|
+
totalUnits={42}
|
|
244
|
+
showMetrics={false}
|
|
245
|
+
animated
|
|
246
|
+
/>
|
|
247
|
+
</div>
|
|
248
|
+
),
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export const NoAnimations: Story = {
|
|
252
|
+
render: () => (
|
|
253
|
+
<div className="p-8 bg-zinc-950 rounded-2xl">
|
|
254
|
+
<WakaServerRack
|
|
255
|
+
servers={sampleServers}
|
|
256
|
+
totalUnits={42}
|
|
257
|
+
showMetrics
|
|
258
|
+
animated={false}
|
|
259
|
+
/>
|
|
260
|
+
</div>
|
|
261
|
+
),
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export const HighLoad: Story = {
|
|
265
|
+
render: () => {
|
|
266
|
+
const highLoadServers: ServerUnit[] = sampleServers.map(server => ({
|
|
267
|
+
...server,
|
|
268
|
+
cpu: Math.min(100, server.cpu + 40),
|
|
269
|
+
ram: Math.min(100, server.ram + 30),
|
|
270
|
+
disk: Math.min(100, server.disk + 20),
|
|
271
|
+
status: server.status === 'online' && server.cpu > 70 ? 'warning' : server.status,
|
|
272
|
+
}))
|
|
273
|
+
|
|
274
|
+
return (
|
|
275
|
+
<div className="p-8 bg-zinc-950 rounded-2xl">
|
|
276
|
+
<div className="mb-4">
|
|
277
|
+
<span className="text-red-400 text-sm font-medium">⚠️ High load detected</span>
|
|
278
|
+
</div>
|
|
279
|
+
<WakaServerRack
|
|
280
|
+
servers={highLoadServers}
|
|
281
|
+
totalUnits={42}
|
|
282
|
+
showMetrics
|
|
283
|
+
animated
|
|
284
|
+
/>
|
|
285
|
+
</div>
|
|
286
|
+
)
|
|
287
|
+
},
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export const DataCenterDashboard: Story = {
|
|
291
|
+
render: () => {
|
|
292
|
+
const [selected, setSelected] = React.useState<string | null>(null)
|
|
293
|
+
const selectedServer = sampleServers.find(s => s.id === selected)
|
|
294
|
+
|
|
295
|
+
return (
|
|
296
|
+
<div className="flex gap-6 p-6 bg-zinc-950 rounded-2xl">
|
|
297
|
+
<div>
|
|
298
|
+
<h3 className="text-zinc-100 font-semibold mb-2">Rack A-01</h3>
|
|
299
|
+
<p className="text-zinc-500 text-sm mb-4">
|
|
300
|
+
{sampleServers.filter(s => s.status === 'online').length} online,{' '}
|
|
301
|
+
{sampleServers.filter(s => s.status === 'warning').length} warning,{' '}
|
|
302
|
+
{sampleServers.filter(s => s.status === 'offline').length} offline
|
|
303
|
+
</p>
|
|
304
|
+
<WakaServerRack
|
|
305
|
+
servers={sampleServers}
|
|
306
|
+
totalUnits={42}
|
|
307
|
+
selectedServer={selected || undefined}
|
|
308
|
+
onServerClick={(id) => setSelected(prev => prev === id ? null : id)}
|
|
309
|
+
showMetrics
|
|
310
|
+
animated
|
|
311
|
+
/>
|
|
312
|
+
</div>
|
|
313
|
+
|
|
314
|
+
{selectedServer && (
|
|
315
|
+
<div className="w-64 p-4 bg-zinc-900 rounded-lg border border-zinc-700">
|
|
316
|
+
<h4 className="font-semibold text-zinc-100 mb-4">{selectedServer.name}</h4>
|
|
317
|
+
<div className="space-y-3 text-sm">
|
|
318
|
+
<div className="flex justify-between">
|
|
319
|
+
<span className="text-zinc-400">Status</span>
|
|
320
|
+
<span className={
|
|
321
|
+
selectedServer.status === 'online' ? 'text-green-400' :
|
|
322
|
+
selectedServer.status === 'warning' ? 'text-yellow-400' :
|
|
323
|
+
selectedServer.status === 'offline' ? 'text-red-400' :
|
|
324
|
+
'text-blue-400'
|
|
325
|
+
}>
|
|
326
|
+
{selectedServer.status.charAt(0).toUpperCase() + selectedServer.status.slice(1)}
|
|
327
|
+
</span>
|
|
328
|
+
</div>
|
|
329
|
+
<div className="flex justify-between">
|
|
330
|
+
<span className="text-zinc-400">Position</span>
|
|
331
|
+
<span className="text-zinc-200">U{selectedServer.unit}-U{selectedServer.unit + selectedServer.height - 1}</span>
|
|
332
|
+
</div>
|
|
333
|
+
<div className="flex justify-between">
|
|
334
|
+
<span className="text-zinc-400">Size</span>
|
|
335
|
+
<span className="text-zinc-200">{selectedServer.height}U</span>
|
|
336
|
+
</div>
|
|
337
|
+
<div className="border-t border-zinc-700 my-3" />
|
|
338
|
+
<div className="space-y-2">
|
|
339
|
+
<div className="flex justify-between items-center">
|
|
340
|
+
<span className="text-zinc-400">CPU</span>
|
|
341
|
+
<div className="flex items-center gap-2">
|
|
342
|
+
<div className="w-20 h-1.5 rounded-full bg-zinc-800">
|
|
343
|
+
<div
|
|
344
|
+
className="h-full rounded-full bg-blue-500"
|
|
345
|
+
style={{ width: `${selectedServer.cpu}%` }}
|
|
346
|
+
/>
|
|
347
|
+
</div>
|
|
348
|
+
<span className="text-zinc-200 w-8 text-right">{selectedServer.cpu}%</span>
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
<div className="flex justify-between items-center">
|
|
352
|
+
<span className="text-zinc-400">RAM</span>
|
|
353
|
+
<div className="flex items-center gap-2">
|
|
354
|
+
<div className="w-20 h-1.5 rounded-full bg-zinc-800">
|
|
355
|
+
<div
|
|
356
|
+
className="h-full rounded-full bg-purple-500"
|
|
357
|
+
style={{ width: `${selectedServer.ram}%` }}
|
|
358
|
+
/>
|
|
359
|
+
</div>
|
|
360
|
+
<span className="text-zinc-200 w-8 text-right">{selectedServer.ram}%</span>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
<div className="flex justify-between items-center">
|
|
364
|
+
<span className="text-zinc-400">Disk</span>
|
|
365
|
+
<div className="flex items-center gap-2">
|
|
366
|
+
<div className="w-20 h-1.5 rounded-full bg-zinc-800">
|
|
367
|
+
<div
|
|
368
|
+
className="h-full rounded-full bg-cyan-500"
|
|
369
|
+
style={{ width: `${selectedServer.disk}%` }}
|
|
370
|
+
/>
|
|
371
|
+
</div>
|
|
372
|
+
<span className="text-zinc-200 w-8 text-right">{selectedServer.disk}%</span>
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
)}
|
|
379
|
+
</div>
|
|
380
|
+
)
|
|
381
|
+
},
|
|
382
|
+
}
|