@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,437 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { WakaDrawer, useDrawer, DrawerHeader, DrawerBody, DrawerFooter } from './index'
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { Button } from '../button'
|
|
5
|
+
import { Settings, User, Bell, Shield, CreditCard, LogOut, Menu, Filter, X } from 'lucide-react'
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof WakaDrawer> = {
|
|
8
|
+
title: 'Components/Display/WakaDrawer',
|
|
9
|
+
component: WakaDrawer,
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'centered',
|
|
12
|
+
docs: {
|
|
13
|
+
description: {
|
|
14
|
+
component: 'A slide-out drawer component with multiple positions, sizes, and customization options.',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
tags: ['autodocs'],
|
|
19
|
+
argTypes: {
|
|
20
|
+
position: {
|
|
21
|
+
control: 'select',
|
|
22
|
+
options: ['left', 'right', 'top', 'bottom'],
|
|
23
|
+
description: 'Position of the drawer',
|
|
24
|
+
},
|
|
25
|
+
size: {
|
|
26
|
+
control: 'select',
|
|
27
|
+
options: ['sm', 'md', 'lg', 'xl', 'full'],
|
|
28
|
+
description: 'Size of the drawer',
|
|
29
|
+
},
|
|
30
|
+
showCloseButton: {
|
|
31
|
+
control: 'boolean',
|
|
32
|
+
description: 'Show close button',
|
|
33
|
+
},
|
|
34
|
+
closeOnOverlayClick: {
|
|
35
|
+
control: 'boolean',
|
|
36
|
+
description: 'Close when clicking overlay',
|
|
37
|
+
},
|
|
38
|
+
showOverlay: {
|
|
39
|
+
control: 'boolean',
|
|
40
|
+
description: 'Show overlay backdrop',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default meta
|
|
46
|
+
type Story = StoryObj<typeof WakaDrawer>
|
|
47
|
+
|
|
48
|
+
export const Default: Story = {
|
|
49
|
+
args: {
|
|
50
|
+
title: 'Drawer Title',
|
|
51
|
+
description: 'This is a drawer description.',
|
|
52
|
+
},
|
|
53
|
+
render: (args) => {
|
|
54
|
+
const [open, setOpen] = React.useState(false)
|
|
55
|
+
return (
|
|
56
|
+
<>
|
|
57
|
+
<Button onClick={() => setOpen(true)}>Open Drawer</Button>
|
|
58
|
+
<WakaDrawer
|
|
59
|
+
{...args}
|
|
60
|
+
open={open}
|
|
61
|
+
onOpenChange={setOpen}
|
|
62
|
+
>
|
|
63
|
+
<p>Drawer content goes here.</p>
|
|
64
|
+
</WakaDrawer>
|
|
65
|
+
</>
|
|
66
|
+
)
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const Positions: Story = {
|
|
71
|
+
render: () => {
|
|
72
|
+
const [position, setPosition] = React.useState<'left' | 'right' | 'top' | 'bottom'>('right')
|
|
73
|
+
const [open, setOpen] = React.useState(false)
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div className="space-y-4">
|
|
77
|
+
<div className="flex gap-2">
|
|
78
|
+
{(['left', 'right', 'top', 'bottom'] as const).map((pos) => (
|
|
79
|
+
<Button
|
|
80
|
+
key={pos}
|
|
81
|
+
variant={position === pos ? 'default' : 'outline'}
|
|
82
|
+
onClick={() => {
|
|
83
|
+
setPosition(pos)
|
|
84
|
+
setOpen(true)
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
{pos.charAt(0).toUpperCase() + pos.slice(1)}
|
|
88
|
+
</Button>
|
|
89
|
+
))}
|
|
90
|
+
</div>
|
|
91
|
+
<WakaDrawer
|
|
92
|
+
open={open}
|
|
93
|
+
onOpenChange={setOpen}
|
|
94
|
+
position={position}
|
|
95
|
+
title={`${position.charAt(0).toUpperCase() + position.slice(1)} Drawer`}
|
|
96
|
+
description="This drawer slides in from the selected position."
|
|
97
|
+
>
|
|
98
|
+
<p>Drawer content for {position} position.</p>
|
|
99
|
+
</WakaDrawer>
|
|
100
|
+
</div>
|
|
101
|
+
)
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const Sizes: Story = {
|
|
106
|
+
render: () => {
|
|
107
|
+
const [size, setSize] = React.useState<'sm' | 'md' | 'lg' | 'xl' | 'full'>('md')
|
|
108
|
+
const [open, setOpen] = React.useState(false)
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<div className="space-y-4">
|
|
112
|
+
<div className="flex gap-2 flex-wrap">
|
|
113
|
+
{(['sm', 'md', 'lg', 'xl', 'full'] as const).map((s) => (
|
|
114
|
+
<Button
|
|
115
|
+
key={s}
|
|
116
|
+
variant={size === s ? 'default' : 'outline'}
|
|
117
|
+
onClick={() => {
|
|
118
|
+
setSize(s)
|
|
119
|
+
setOpen(true)
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
{s.toUpperCase()}
|
|
123
|
+
</Button>
|
|
124
|
+
))}
|
|
125
|
+
</div>
|
|
126
|
+
<WakaDrawer
|
|
127
|
+
open={open}
|
|
128
|
+
onOpenChange={setOpen}
|
|
129
|
+
size={size}
|
|
130
|
+
title={`Size: ${size.toUpperCase()}`}
|
|
131
|
+
description="Demonstrating different drawer sizes."
|
|
132
|
+
>
|
|
133
|
+
<p>This drawer has size "{size}".</p>
|
|
134
|
+
</WakaDrawer>
|
|
135
|
+
</div>
|
|
136
|
+
)
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export const WithFooter: Story = {
|
|
141
|
+
render: () => {
|
|
142
|
+
const [open, setOpen] = React.useState(false)
|
|
143
|
+
return (
|
|
144
|
+
<>
|
|
145
|
+
<Button onClick={() => setOpen(true)}>Open with Footer</Button>
|
|
146
|
+
<WakaDrawer
|
|
147
|
+
open={open}
|
|
148
|
+
onOpenChange={setOpen}
|
|
149
|
+
title="Edit Profile"
|
|
150
|
+
description="Make changes to your profile information."
|
|
151
|
+
footer={
|
|
152
|
+
<>
|
|
153
|
+
<Button variant="outline" onClick={() => setOpen(false)}>
|
|
154
|
+
Cancel
|
|
155
|
+
</Button>
|
|
156
|
+
<Button onClick={() => setOpen(false)}>
|
|
157
|
+
Save Changes
|
|
158
|
+
</Button>
|
|
159
|
+
</>
|
|
160
|
+
}
|
|
161
|
+
>
|
|
162
|
+
<div className="space-y-4">
|
|
163
|
+
<div>
|
|
164
|
+
<label className="text-sm font-medium">Name</label>
|
|
165
|
+
<input
|
|
166
|
+
type="text"
|
|
167
|
+
className="w-full mt-1 px-3 py-2 border rounded-md"
|
|
168
|
+
defaultValue="John Doe"
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
<div>
|
|
172
|
+
<label className="text-sm font-medium">Email</label>
|
|
173
|
+
<input
|
|
174
|
+
type="email"
|
|
175
|
+
className="w-full mt-1 px-3 py-2 border rounded-md"
|
|
176
|
+
defaultValue="john@example.com"
|
|
177
|
+
/>
|
|
178
|
+
</div>
|
|
179
|
+
<div>
|
|
180
|
+
<label className="text-sm font-medium">Bio</label>
|
|
181
|
+
<textarea
|
|
182
|
+
className="w-full mt-1 px-3 py-2 border rounded-md"
|
|
183
|
+
rows={3}
|
|
184
|
+
defaultValue="Software developer passionate about React."
|
|
185
|
+
/>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
</WakaDrawer>
|
|
189
|
+
</>
|
|
190
|
+
)
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export const NavigationMenu: Story = {
|
|
195
|
+
render: () => {
|
|
196
|
+
const [open, setOpen] = React.useState(false)
|
|
197
|
+
|
|
198
|
+
const menuItems = [
|
|
199
|
+
{ icon: <User className="h-5 w-5" />, label: 'Profile', href: '#' },
|
|
200
|
+
{ icon: <Settings className="h-5 w-5" />, label: 'Settings', href: '#' },
|
|
201
|
+
{ icon: <Bell className="h-5 w-5" />, label: 'Notifications', href: '#' },
|
|
202
|
+
{ icon: <Shield className="h-5 w-5" />, label: 'Security', href: '#' },
|
|
203
|
+
{ icon: <CreditCard className="h-5 w-5" />, label: 'Billing', href: '#' },
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<>
|
|
208
|
+
<Button variant="ghost" size="icon" onClick={() => setOpen(true)}>
|
|
209
|
+
<Menu className="h-5 w-5" />
|
|
210
|
+
</Button>
|
|
211
|
+
<WakaDrawer
|
|
212
|
+
open={open}
|
|
213
|
+
onOpenChange={setOpen}
|
|
214
|
+
position="left"
|
|
215
|
+
size="sm"
|
|
216
|
+
title="Menu"
|
|
217
|
+
>
|
|
218
|
+
<nav className="space-y-1">
|
|
219
|
+
{menuItems.map((item) => (
|
|
220
|
+
<a
|
|
221
|
+
key={item.label}
|
|
222
|
+
href={item.href}
|
|
223
|
+
className="flex items-center gap-3 px-3 py-2 rounded-md hover:bg-muted transition-colors"
|
|
224
|
+
onClick={() => setOpen(false)}
|
|
225
|
+
>
|
|
226
|
+
{item.icon}
|
|
227
|
+
<span>{item.label}</span>
|
|
228
|
+
</a>
|
|
229
|
+
))}
|
|
230
|
+
</nav>
|
|
231
|
+
<div className="mt-auto pt-4 border-t">
|
|
232
|
+
<button className="flex items-center gap-3 px-3 py-2 w-full text-destructive hover:bg-destructive/10 rounded-md transition-colors">
|
|
233
|
+
<LogOut className="h-5 w-5" />
|
|
234
|
+
<span>Sign out</span>
|
|
235
|
+
</button>
|
|
236
|
+
</div>
|
|
237
|
+
</WakaDrawer>
|
|
238
|
+
</>
|
|
239
|
+
)
|
|
240
|
+
},
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export const FilterPanel: Story = {
|
|
244
|
+
render: () => {
|
|
245
|
+
const [open, setOpen] = React.useState(false)
|
|
246
|
+
const [filters, setFilters] = React.useState({
|
|
247
|
+
category: '',
|
|
248
|
+
priceRange: [0, 100],
|
|
249
|
+
inStock: false,
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<>
|
|
254
|
+
<Button variant="outline" onClick={() => setOpen(true)}>
|
|
255
|
+
<Filter className="h-4 w-4 mr-2" />
|
|
256
|
+
Filters
|
|
257
|
+
</Button>
|
|
258
|
+
<WakaDrawer
|
|
259
|
+
open={open}
|
|
260
|
+
onOpenChange={setOpen}
|
|
261
|
+
position="right"
|
|
262
|
+
title="Filters"
|
|
263
|
+
description="Refine your search results"
|
|
264
|
+
footer={
|
|
265
|
+
<>
|
|
266
|
+
<Button
|
|
267
|
+
variant="outline"
|
|
268
|
+
onClick={() =>
|
|
269
|
+
setFilters({ category: '', priceRange: [0, 100], inStock: false })
|
|
270
|
+
}
|
|
271
|
+
>
|
|
272
|
+
Reset
|
|
273
|
+
</Button>
|
|
274
|
+
<Button onClick={() => setOpen(false)}>Apply Filters</Button>
|
|
275
|
+
</>
|
|
276
|
+
}
|
|
277
|
+
>
|
|
278
|
+
<div className="space-y-6">
|
|
279
|
+
<div>
|
|
280
|
+
<label className="text-sm font-medium">Category</label>
|
|
281
|
+
<select
|
|
282
|
+
className="w-full mt-1 px-3 py-2 border rounded-md"
|
|
283
|
+
value={filters.category}
|
|
284
|
+
onChange={(e) =>
|
|
285
|
+
setFilters({ ...filters, category: e.target.value })
|
|
286
|
+
}
|
|
287
|
+
>
|
|
288
|
+
<option value="">All Categories</option>
|
|
289
|
+
<option value="electronics">Electronics</option>
|
|
290
|
+
<option value="clothing">Clothing</option>
|
|
291
|
+
<option value="books">Books</option>
|
|
292
|
+
<option value="home">Home & Garden</option>
|
|
293
|
+
</select>
|
|
294
|
+
</div>
|
|
295
|
+
<div>
|
|
296
|
+
<label className="text-sm font-medium">Price Range</label>
|
|
297
|
+
<div className="flex items-center gap-2 mt-1">
|
|
298
|
+
<input
|
|
299
|
+
type="number"
|
|
300
|
+
className="w-full px-3 py-2 border rounded-md"
|
|
301
|
+
placeholder="Min"
|
|
302
|
+
value={filters.priceRange[0]}
|
|
303
|
+
onChange={(e) =>
|
|
304
|
+
setFilters({
|
|
305
|
+
...filters,
|
|
306
|
+
priceRange: [Number(e.target.value), filters.priceRange[1]],
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
/>
|
|
310
|
+
<span>-</span>
|
|
311
|
+
<input
|
|
312
|
+
type="number"
|
|
313
|
+
className="w-full px-3 py-2 border rounded-md"
|
|
314
|
+
placeholder="Max"
|
|
315
|
+
value={filters.priceRange[1]}
|
|
316
|
+
onChange={(e) =>
|
|
317
|
+
setFilters({
|
|
318
|
+
...filters,
|
|
319
|
+
priceRange: [filters.priceRange[0], Number(e.target.value)],
|
|
320
|
+
})
|
|
321
|
+
}
|
|
322
|
+
/>
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
<div className="flex items-center gap-2">
|
|
326
|
+
<input
|
|
327
|
+
type="checkbox"
|
|
328
|
+
id="inStock"
|
|
329
|
+
checked={filters.inStock}
|
|
330
|
+
onChange={(e) =>
|
|
331
|
+
setFilters({ ...filters, inStock: e.target.checked })
|
|
332
|
+
}
|
|
333
|
+
/>
|
|
334
|
+
<label htmlFor="inStock" className="text-sm">
|
|
335
|
+
Only show in-stock items
|
|
336
|
+
</label>
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
</WakaDrawer>
|
|
340
|
+
</>
|
|
341
|
+
)
|
|
342
|
+
},
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export const WithHook: Story = {
|
|
346
|
+
render: () => {
|
|
347
|
+
const drawer = useDrawer({
|
|
348
|
+
onOpen: () => console.log('Drawer opened'),
|
|
349
|
+
onClose: () => console.log('Drawer closed'),
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
return (
|
|
353
|
+
<div className="space-y-4">
|
|
354
|
+
<div className="flex gap-2">
|
|
355
|
+
<Button onClick={drawer.openDrawer}>Open</Button>
|
|
356
|
+
<Button variant="outline" onClick={drawer.toggleDrawer}>Toggle</Button>
|
|
357
|
+
</div>
|
|
358
|
+
<WakaDrawer
|
|
359
|
+
{...drawer}
|
|
360
|
+
title="Using useDrawer Hook"
|
|
361
|
+
description="This drawer is controlled by the useDrawer hook."
|
|
362
|
+
>
|
|
363
|
+
<p>The drawer state is managed by the hook.</p>
|
|
364
|
+
<Button
|
|
365
|
+
variant="outline"
|
|
366
|
+
className="mt-4"
|
|
367
|
+
onClick={drawer.closeDrawer}
|
|
368
|
+
>
|
|
369
|
+
Close Drawer
|
|
370
|
+
</Button>
|
|
371
|
+
</WakaDrawer>
|
|
372
|
+
</div>
|
|
373
|
+
)
|
|
374
|
+
},
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export const NoOverlay: Story = {
|
|
378
|
+
render: () => {
|
|
379
|
+
const [open, setOpen] = React.useState(false)
|
|
380
|
+
return (
|
|
381
|
+
<>
|
|
382
|
+
<Button onClick={() => setOpen(true)}>Open Without Overlay</Button>
|
|
383
|
+
<WakaDrawer
|
|
384
|
+
open={open}
|
|
385
|
+
onOpenChange={setOpen}
|
|
386
|
+
showOverlay={false}
|
|
387
|
+
title="No Overlay"
|
|
388
|
+
description="This drawer doesn't have a backdrop overlay."
|
|
389
|
+
>
|
|
390
|
+
<p>You can still interact with the page content behind.</p>
|
|
391
|
+
</WakaDrawer>
|
|
392
|
+
</>
|
|
393
|
+
)
|
|
394
|
+
},
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export const CustomContent: Story = {
|
|
398
|
+
render: () => {
|
|
399
|
+
const [open, setOpen] = React.useState(false)
|
|
400
|
+
return (
|
|
401
|
+
<>
|
|
402
|
+
<Button onClick={() => setOpen(true)}>Open Custom Drawer</Button>
|
|
403
|
+
<WakaDrawer
|
|
404
|
+
open={open}
|
|
405
|
+
onOpenChange={setOpen}
|
|
406
|
+
size="lg"
|
|
407
|
+
>
|
|
408
|
+
<div className="h-full flex flex-col">
|
|
409
|
+
<div className="p-4 border-b flex items-center justify-between">
|
|
410
|
+
<h2 className="font-semibold text-lg">Custom Layout</h2>
|
|
411
|
+
<Button variant="ghost" size="icon" onClick={() => setOpen(false)}>
|
|
412
|
+
<X className="h-4 w-4" />
|
|
413
|
+
</Button>
|
|
414
|
+
</div>
|
|
415
|
+
<div className="flex-1 overflow-auto p-4">
|
|
416
|
+
<p className="text-muted-foreground mb-4">
|
|
417
|
+
This drawer uses custom layout without the built-in header/footer.
|
|
418
|
+
</p>
|
|
419
|
+
<div className="grid grid-cols-2 gap-4">
|
|
420
|
+
{Array.from({ length: 6 }).map((_, i) => (
|
|
421
|
+
<div key={i} className="border rounded-lg p-4 bg-muted/30">
|
|
422
|
+
Card {i + 1}
|
|
423
|
+
</div>
|
|
424
|
+
))}
|
|
425
|
+
</div>
|
|
426
|
+
</div>
|
|
427
|
+
<div className="p-4 border-t bg-muted/30">
|
|
428
|
+
<Button className="w-full" onClick={() => setOpen(false)}>
|
|
429
|
+
Done
|
|
430
|
+
</Button>
|
|
431
|
+
</div>
|
|
432
|
+
</div>
|
|
433
|
+
</WakaDrawer>
|
|
434
|
+
</>
|
|
435
|
+
)
|
|
436
|
+
},
|
|
437
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { WakaEnvVarEditor, defaultEnvVariables } from './index'
|
|
3
|
+
import type { EnvVariable } from './index'
|
|
4
|
+
import * as React from 'react'
|
|
5
|
+
|
|
6
|
+
const simpleVariables: EnvVariable[] = [
|
|
7
|
+
{ key: 'NODE_ENV', value: 'development' },
|
|
8
|
+
{ key: 'PORT', value: '3000' },
|
|
9
|
+
{ key: 'DEBUG', value: 'true' },
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
const productionVariables: EnvVariable[] = [
|
|
13
|
+
{ key: 'NODE_ENV', value: 'production', isRequired: true },
|
|
14
|
+
{ key: 'DATABASE_URL', value: 'postgresql://prod-user:secure-pass@db.example.com:5432/production', isSecret: true, isRequired: true },
|
|
15
|
+
{ key: 'REDIS_URL', value: 'redis://redis.example.com:6379', isSecret: true },
|
|
16
|
+
{ key: 'API_KEY', value: 'pk_live_1234567890abcdefghijklmnopqrstuvwxyz', isSecret: true, isRequired: true },
|
|
17
|
+
{ key: 'SECRET_KEY', value: 'sk_live_9876543210zyxwvutsrqponmlkjihgfedcba', isSecret: true, isRequired: true },
|
|
18
|
+
{ key: 'LOG_LEVEL', value: 'warn' },
|
|
19
|
+
{ key: 'SENTRY_DSN', value: 'https://abc123@sentry.io/12345', isSecret: true },
|
|
20
|
+
{ key: 'ENABLE_METRICS', value: 'true' },
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
const variablesWithSources: EnvVariable[] = [
|
|
24
|
+
{ key: 'DATABASE_URL', value: 'postgresql://localhost:5432/dev', source: 'local', isSecret: true },
|
|
25
|
+
{ key: 'DATABASE_URL', value: 'postgresql://staging-db:5432/staging', source: 'staging', isSecret: true },
|
|
26
|
+
{ key: 'DATABASE_URL', value: 'postgresql://prod-db:5432/production', source: 'production', isSecret: true },
|
|
27
|
+
{ key: 'API_KEY', value: 'dev_key_123', source: 'local', isSecret: true },
|
|
28
|
+
{ key: 'API_KEY', value: 'staging_key_456', source: 'staging', isSecret: true },
|
|
29
|
+
{ key: 'API_KEY', value: 'prod_key_789', source: 'production', isSecret: true },
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
const incompleteVariables: EnvVariable[] = [
|
|
33
|
+
{ key: 'NODE_ENV', value: 'production', isRequired: true },
|
|
34
|
+
{ key: 'DATABASE_URL', value: '', isSecret: true, isRequired: true },
|
|
35
|
+
{ key: 'API_KEY', value: '', isSecret: true, isRequired: true },
|
|
36
|
+
{ key: 'OPTIONAL_VAR', value: '' },
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
const meta: Meta<typeof WakaEnvVarEditor> = {
|
|
40
|
+
title: 'Components/DevOps/WakaEnvVarEditor',
|
|
41
|
+
component: WakaEnvVarEditor,
|
|
42
|
+
parameters: {
|
|
43
|
+
layout: 'centered',
|
|
44
|
+
docs: {
|
|
45
|
+
description: {
|
|
46
|
+
component: 'An environment variable editor with secret masking, import/export, search, and validation for required variables.',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
tags: ['autodocs'],
|
|
51
|
+
argTypes: {
|
|
52
|
+
readOnly: {
|
|
53
|
+
control: 'boolean',
|
|
54
|
+
description: 'Read-only mode',
|
|
55
|
+
},
|
|
56
|
+
showSource: {
|
|
57
|
+
control: 'boolean',
|
|
58
|
+
description: 'Show source column',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default meta
|
|
64
|
+
type Story = StoryObj<typeof WakaEnvVarEditor>
|
|
65
|
+
|
|
66
|
+
export const Default: Story = {
|
|
67
|
+
args: {
|
|
68
|
+
variables: defaultEnvVariables,
|
|
69
|
+
},
|
|
70
|
+
render: (args) => (
|
|
71
|
+
<div className="w-[700px] h-[500px]">
|
|
72
|
+
<WakaEnvVarEditor
|
|
73
|
+
{...args}
|
|
74
|
+
onChange={(vars) => console.log('Change:', vars)}
|
|
75
|
+
onSave={(vars) => console.log('Save:', vars)}
|
|
76
|
+
onExport={() => console.log('Export')}
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
),
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const Simple: Story = {
|
|
83
|
+
render: () => (
|
|
84
|
+
<div className="w-[700px] h-[400px]">
|
|
85
|
+
<WakaEnvVarEditor
|
|
86
|
+
variables={simpleVariables}
|
|
87
|
+
title="Development Variables"
|
|
88
|
+
onChange={(vars) => console.log('Change:', vars)}
|
|
89
|
+
onSave={(vars) => console.log('Save:', vars)}
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const Production: Story = {
|
|
96
|
+
render: () => (
|
|
97
|
+
<div className="w-[700px] h-[500px]">
|
|
98
|
+
<WakaEnvVarEditor
|
|
99
|
+
variables={productionVariables}
|
|
100
|
+
title="Production Environment"
|
|
101
|
+
onChange={(vars) => console.log('Change:', vars)}
|
|
102
|
+
onSave={(vars) => console.log('Save:', vars)}
|
|
103
|
+
onExport={() => console.log('Export')}
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
),
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export const WithSources: Story = {
|
|
110
|
+
render: () => (
|
|
111
|
+
<div className="w-[750px] h-[500px]">
|
|
112
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
113
|
+
Variables from different environments
|
|
114
|
+
</p>
|
|
115
|
+
<WakaEnvVarEditor
|
|
116
|
+
variables={variablesWithSources}
|
|
117
|
+
showSource
|
|
118
|
+
title="Multi-Environment Variables"
|
|
119
|
+
onChange={(vars) => console.log('Change:', vars)}
|
|
120
|
+
/>
|
|
121
|
+
</div>
|
|
122
|
+
),
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const MissingRequired: Story = {
|
|
126
|
+
render: () => (
|
|
127
|
+
<div className="w-[700px] h-[400px]">
|
|
128
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
129
|
+
Shows warning indicators for empty required variables
|
|
130
|
+
</p>
|
|
131
|
+
<WakaEnvVarEditor
|
|
132
|
+
variables={incompleteVariables}
|
|
133
|
+
title="Incomplete Configuration"
|
|
134
|
+
onChange={(vars) => console.log('Change:', vars)}
|
|
135
|
+
onSave={(vars) => console.log('Save:', vars)}
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
138
|
+
),
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const ReadOnly: Story = {
|
|
142
|
+
render: () => (
|
|
143
|
+
<div className="w-[700px] h-[500px]">
|
|
144
|
+
<WakaEnvVarEditor
|
|
145
|
+
variables={productionVariables}
|
|
146
|
+
readOnly
|
|
147
|
+
title="Production Variables (Read Only)"
|
|
148
|
+
onExport={() => console.log('Export')}
|
|
149
|
+
/>
|
|
150
|
+
</div>
|
|
151
|
+
),
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export const Interactive: Story = {
|
|
155
|
+
render: () => {
|
|
156
|
+
const [variables, setVariables] = React.useState<EnvVariable[]>(simpleVariables)
|
|
157
|
+
const [saved, setSaved] = React.useState(false)
|
|
158
|
+
|
|
159
|
+
const handleSave = (vars: EnvVariable[]) => {
|
|
160
|
+
console.log('Saving:', vars)
|
|
161
|
+
setSaved(true)
|
|
162
|
+
setTimeout(() => setSaved(false), 2000)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<div className="w-[700px] h-[500px]">
|
|
167
|
+
{saved && (
|
|
168
|
+
<div className="mb-4 p-2 bg-green-500/10 border border-green-500/30 rounded text-sm text-green-500">
|
|
169
|
+
Variables saved successfully!
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
<WakaEnvVarEditor
|
|
173
|
+
variables={variables}
|
|
174
|
+
onChange={setVariables}
|
|
175
|
+
onSave={handleSave}
|
|
176
|
+
onExport={() => {
|
|
177
|
+
const content = variables
|
|
178
|
+
.map((v) => `${v.key}=${v.value}`)
|
|
179
|
+
.join('\n')
|
|
180
|
+
console.log('Export content:\n', content)
|
|
181
|
+
alert('Check console for exported content')
|
|
182
|
+
}}
|
|
183
|
+
title="Interactive Editor"
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
)
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export const Empty: Story = {
|
|
191
|
+
render: () => (
|
|
192
|
+
<div className="w-[700px] h-[400px]">
|
|
193
|
+
<WakaEnvVarEditor
|
|
194
|
+
variables={[]}
|
|
195
|
+
title="No Variables"
|
|
196
|
+
onChange={(vars) => console.log('Change:', vars)}
|
|
197
|
+
onSave={(vars) => console.log('Save:', vars)}
|
|
198
|
+
/>
|
|
199
|
+
</div>
|
|
200
|
+
),
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export const ManyVariables: Story = {
|
|
204
|
+
render: () => {
|
|
205
|
+
const manyVars: EnvVariable[] = Array.from({ length: 30 }, (_, i) => ({
|
|
206
|
+
key: `VARIABLE_${i + 1}`,
|
|
207
|
+
value: `value_${i + 1}`,
|
|
208
|
+
isSecret: i % 3 === 0,
|
|
209
|
+
isRequired: i % 5 === 0,
|
|
210
|
+
}))
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<div className="w-[700px] h-[600px]">
|
|
214
|
+
<WakaEnvVarEditor
|
|
215
|
+
variables={manyVars}
|
|
216
|
+
title="Large Configuration"
|
|
217
|
+
onChange={(vars) => console.log('Change:', vars)}
|
|
218
|
+
onSave={(vars) => console.log('Save:', vars)}
|
|
219
|
+
onExport={() => console.log('Export')}
|
|
220
|
+
/>
|
|
221
|
+
</div>
|
|
222
|
+
)
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export const ConfigurationPanel: Story = {
|
|
227
|
+
render: () => {
|
|
228
|
+
const [variables, setVariables] = React.useState<EnvVariable[]>(defaultEnvVariables)
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<div className="p-6 rounded-xl border bg-card">
|
|
232
|
+
<div className="flex items-center justify-between mb-6">
|
|
233
|
+
<div>
|
|
234
|
+
<h2 className="text-xl font-bold">Application Configuration</h2>
|
|
235
|
+
<p className="text-sm text-muted-foreground">Environment: Production</p>
|
|
236
|
+
</div>
|
|
237
|
+
<div className="flex items-center gap-2 text-sm">
|
|
238
|
+
<span className="text-muted-foreground">{variables.length} variables</span>
|
|
239
|
+
<span className="text-muted-foreground">•</span>
|
|
240
|
+
<span className="text-yellow-500">{variables.filter((v) => v.isSecret).length} secrets</span>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
<div className="w-[650px]">
|
|
245
|
+
<WakaEnvVarEditor
|
|
246
|
+
variables={variables}
|
|
247
|
+
onChange={setVariables}
|
|
248
|
+
onSave={(vars) => {
|
|
249
|
+
console.log('Saving configuration:', vars)
|
|
250
|
+
alert(`Saved ${vars.length} variables!`)
|
|
251
|
+
}}
|
|
252
|
+
onExport={() => {
|
|
253
|
+
const content = variables
|
|
254
|
+
.map((v) => `${v.key}=${v.isSecret ? '***' : v.value}`)
|
|
255
|
+
.join('\n')
|
|
256
|
+
console.log('Export (secrets masked):\n', content)
|
|
257
|
+
}}
|
|
258
|
+
/>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
)
|
|
262
|
+
},
|
|
263
|
+
}
|