hazo_config 1.0.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.
@@ -0,0 +1,214 @@
1
+ // Config editor component for managing configuration
2
+ // Provides a more advanced interface for editing configuration values
3
+
4
+ import React, { useState, useEffect } from 'react'
5
+ import { cn } from '@/lib/utils'
6
+ import type { ConfigProvider } from '@/lib/types'
7
+ import { RefreshCw, Save } from 'lucide-react'
8
+
9
+ /**
10
+ * Config editor component props
11
+ */
12
+ export interface ConfigEditorProps {
13
+ /**
14
+ * The config provider instance
15
+ */
16
+ config_provider: ConfigProvider
17
+ /**
18
+ * Additional CSS classes
19
+ */
20
+ className?: string
21
+ /**
22
+ * Callback when config is updated
23
+ */
24
+ on_update?: () => void
25
+ }
26
+
27
+ /**
28
+ * Config editor component
29
+ * Provides interface for viewing and editing configuration with refresh and save capabilities
30
+ * @param props - Component props
31
+ * @returns React component
32
+ */
33
+ export const ConfigEditor: React.FC<ConfigEditorProps> = ({
34
+ config_provider,
35
+ className,
36
+ on_update,
37
+ }) => {
38
+ const [sections, set_sections] = useState<Record<string, Record<string, string>>>({})
39
+ const [selected_section, set_selected_section] = useState<string | null>(null)
40
+ const [new_section_name, set_new_section_name] = useState<string>('')
41
+ const [new_key, set_new_key] = useState<string>('')
42
+ const [new_value, set_new_value] = useState<string>('')
43
+
44
+ /**
45
+ * Load configuration from provider
46
+ */
47
+ const load_config = () => {
48
+ const all_sections: Record<string, Record<string, string>> = {}
49
+
50
+ if ('getAllSections' in config_provider && typeof config_provider.getAllSections === 'function') {
51
+ const all = (config_provider as any).getAllSections()
52
+ Object.assign(all_sections, all)
53
+ }
54
+
55
+ set_sections(all_sections)
56
+ if (!selected_section && Object.keys(all_sections).length > 0) {
57
+ set_selected_section(Object.keys(all_sections)[0])
58
+ }
59
+ }
60
+
61
+ useEffect(() => {
62
+ load_config()
63
+ }, [config_provider])
64
+
65
+ /**
66
+ * Handle refresh button click
67
+ */
68
+ const handle_refresh = () => {
69
+ config_provider.refresh()
70
+ load_config()
71
+ if (on_update) {
72
+ on_update()
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Handle save button click
78
+ */
79
+ const handle_save = () => {
80
+ config_provider.save()
81
+ load_config()
82
+ if (on_update) {
83
+ on_update()
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Add a new key-value pair to selected section
89
+ */
90
+ const handle_add_key = () => {
91
+ if (selected_section && new_key && new_value) {
92
+ config_provider.set(selected_section, new_key, new_value)
93
+ set_new_key('')
94
+ set_new_value('')
95
+ load_config()
96
+ if (on_update) {
97
+ on_update()
98
+ }
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Update a config value
104
+ */
105
+ const handle_update_value = (section: string, key: string, value: string) => {
106
+ config_provider.set(section, key, value)
107
+ load_config()
108
+ }
109
+
110
+ const current_section_data = selected_section ? sections[selected_section] : null
111
+
112
+ return (
113
+ <div className={cn('cls_config_editor space-y-4', className)}>
114
+ <div className="cls_config_editor_header flex items-center justify-between border-b pb-2">
115
+ <h2 className="text-xl font-semibold">Configuration Editor</h2>
116
+ <div className="flex gap-2">
117
+ <button
118
+ onClick={handle_refresh}
119
+ className="px-3 py-1 border rounded hover:bg-muted flex items-center gap-2"
120
+ aria-label="Refresh"
121
+ >
122
+ <RefreshCw size={16} />
123
+ Refresh
124
+ </button>
125
+ <button
126
+ onClick={handle_save}
127
+ className="px-3 py-1 bg-primary text-primary-foreground rounded hover:bg-primary/90 flex items-center gap-2"
128
+ aria-label="Save"
129
+ >
130
+ <Save size={16} />
131
+ Save
132
+ </button>
133
+ </div>
134
+ </div>
135
+
136
+ <div className="cls_config_editor_content grid grid-cols-1 md:grid-cols-3 gap-4">
137
+ <div className="cls_config_sections_list border rounded p-4">
138
+ <h3 className="font-semibold mb-2">Sections</h3>
139
+ <div className="space-y-1">
140
+ {Object.keys(sections).map((section_name) => (
141
+ <button
142
+ key={section_name}
143
+ onClick={() => set_selected_section(section_name)}
144
+ className={cn(
145
+ 'w-full text-left px-2 py-1 rounded text-sm',
146
+ selected_section === section_name
147
+ ? 'bg-primary text-primary-foreground'
148
+ : 'hover:bg-muted'
149
+ )}
150
+ >
151
+ {section_name}
152
+ </button>
153
+ ))}
154
+ </div>
155
+ </div>
156
+
157
+ <div className="cls_config_section_content md:col-span-2 border rounded p-4">
158
+ {selected_section ? (
159
+ <>
160
+ <h3 className="font-semibold mb-4">{selected_section}</h3>
161
+ <div className="space-y-3">
162
+ {current_section_data &&
163
+ Object.entries(current_section_data).map(([key, value]) => (
164
+ <div key={key} className="cls_config_item">
165
+ <label className="block text-sm font-medium mb-1">{key}</label>
166
+ <input
167
+ type="text"
168
+ value={value}
169
+ onChange={(e) =>
170
+ handle_update_value(selected_section, key, e.target.value)
171
+ }
172
+ className="w-full px-2 py-1 border rounded"
173
+ />
174
+ </div>
175
+ ))}
176
+
177
+ <div className="cls_add_new_key border-t pt-3 mt-3">
178
+ <h4 className="text-sm font-medium mb-2">Add New Key</h4>
179
+ <div className="space-y-2">
180
+ <input
181
+ type="text"
182
+ placeholder="Key name"
183
+ value={new_key}
184
+ onChange={(e) => set_new_key(e.target.value)}
185
+ className="w-full px-2 py-1 border rounded text-sm"
186
+ />
187
+ <input
188
+ type="text"
189
+ placeholder="Value"
190
+ value={new_value}
191
+ onChange={(e) => set_new_value(e.target.value)}
192
+ className="w-full px-2 py-1 border rounded text-sm"
193
+ />
194
+ <button
195
+ onClick={handle_add_key}
196
+ className="px-3 py-1 bg-primary text-primary-foreground rounded hover:bg-primary/90 text-sm"
197
+ >
198
+ Add Key
199
+ </button>
200
+ </div>
201
+ </div>
202
+ </div>
203
+ </>
204
+ ) : (
205
+ <div className="text-center text-muted-foreground py-8">
206
+ Select a section to view/edit
207
+ </div>
208
+ )}
209
+ </div>
210
+ </div>
211
+ </div>
212
+ )
213
+ }
214
+
@@ -0,0 +1,126 @@
1
+ // Storybook stories for ConfigViewer component
2
+ // Demonstrates the config viewer component with mock config provider
3
+
4
+ import type { Meta, StoryObj } from '@storybook/react'
5
+ import { ConfigViewer } from './config_viewer'
6
+ import { MockConfigProvider } from '@/lib/mock_config_provider'
7
+
8
+ /**
9
+ * Sample configuration data for testing
10
+ */
11
+ const sample_config = {
12
+ database: {
13
+ host: 'localhost',
14
+ port: '5432',
15
+ name: 'myapp_db',
16
+ user: 'admin',
17
+ },
18
+ logging: {
19
+ logfile: 'logs/app.log',
20
+ level: 'info',
21
+ max_size: '10MB',
22
+ },
23
+ api: {
24
+ base_url: 'https://api.example.com',
25
+ timeout: '5000',
26
+ retry_count: '3',
27
+ },
28
+ }
29
+
30
+ /**
31
+ * Meta configuration for ConfigViewer stories
32
+ */
33
+ const meta: Meta<typeof ConfigViewer> = {
34
+ title: 'Components/ConfigViewer',
35
+ component: ConfigViewer,
36
+ parameters: {
37
+ layout: 'padded',
38
+ },
39
+ tags: ['autodocs'],
40
+ argTypes: {
41
+ config_provider: {
42
+ control: false,
43
+ description: 'The config provider instance',
44
+ },
45
+ className: {
46
+ control: 'text',
47
+ description: 'Additional CSS classes',
48
+ },
49
+ },
50
+ }
51
+
52
+ export default meta
53
+ type Story = StoryObj<typeof ConfigViewer>
54
+
55
+ /**
56
+ * Default story with sample configuration
57
+ */
58
+ export const Default: Story = {
59
+ args: {
60
+ config_provider: new MockConfigProvider(sample_config),
61
+ },
62
+ }
63
+
64
+ /**
65
+ * Story with database configuration only
66
+ */
67
+ export const DatabaseConfig: Story = {
68
+ args: {
69
+ config_provider: new MockConfigProvider({
70
+ database: sample_config.database,
71
+ }),
72
+ },
73
+ }
74
+
75
+ /**
76
+ * Story with logging configuration
77
+ */
78
+ export const LoggingConfig: Story = {
79
+ args: {
80
+ config_provider: new MockConfigProvider({
81
+ logging: sample_config.logging,
82
+ }),
83
+ },
84
+ }
85
+
86
+ /**
87
+ * Story with API configuration
88
+ */
89
+ export const ApiConfig: Story = {
90
+ args: {
91
+ config_provider: new MockConfigProvider({
92
+ api: sample_config.api,
93
+ }),
94
+ },
95
+ }
96
+
97
+ /**
98
+ * Story with empty configuration
99
+ */
100
+ export const EmptyConfig: Story = {
101
+ args: {
102
+ config_provider: new MockConfigProvider(),
103
+ },
104
+ }
105
+
106
+ /**
107
+ * Story with multiple sections
108
+ */
109
+ export const MultipleSections: Story = {
110
+ args: {
111
+ config_provider: new MockConfigProvider({
112
+ ...sample_config,
113
+ server: {
114
+ host: '0.0.0.0',
115
+ port: '3000',
116
+ env: 'production',
117
+ },
118
+ app: {
119
+ name: 'My App',
120
+ version: '1.0.0',
121
+ debug: 'false',
122
+ },
123
+ }),
124
+ },
125
+ }
126
+
@@ -0,0 +1,170 @@
1
+ // Config viewer component for displaying configuration data
2
+ // Displays configuration sections and values in a readable format
3
+
4
+ import React, { useState, useEffect } from 'react'
5
+ import { cn } from '@/lib/utils'
6
+ import type { ConfigProvider } from '@/lib/types'
7
+ import { Pencil, CheckCircle2, XCircle } from 'lucide-react'
8
+
9
+ /**
10
+ * Config viewer component props
11
+ */
12
+ export interface ConfigViewerProps {
13
+ /**
14
+ * The config provider instance
15
+ */
16
+ config_provider: ConfigProvider
17
+ /**
18
+ * Additional CSS classes
19
+ */
20
+ className?: string
21
+ /**
22
+ * Callback when config is updated
23
+ */
24
+ on_update?: () => void
25
+ }
26
+
27
+ /**
28
+ * Config viewer component
29
+ * Displays configuration sections and allows viewing/editing values
30
+ * @param props - Component props
31
+ * @returns React component
32
+ */
33
+ export const ConfigViewer: React.FC<ConfigViewerProps> = ({
34
+ config_provider,
35
+ className,
36
+ on_update,
37
+ }) => {
38
+ const [sections, set_sections] = useState<Record<string, Record<string, string>>>({})
39
+ const [editing, set_editing] = useState<{ section: string; key: string } | null>(null)
40
+ const [edit_value, set_edit_value] = useState<string>('')
41
+
42
+ /**
43
+ * Load configuration from provider
44
+ */
45
+ const load_config = () => {
46
+ const all_sections: Record<string, Record<string, string>> = {}
47
+
48
+ // Try to get all sections - if provider has getAllSections, use it
49
+ if ('getAllSections' in config_provider && typeof config_provider.getAllSections === 'function') {
50
+ const all = (config_provider as any).getAllSections()
51
+ Object.assign(all_sections, all)
52
+ } else {
53
+ // Fallback: try common section names
54
+ const common_sections = ['database', 'logging', 'api', 'server', 'app']
55
+ for (const section of common_sections) {
56
+ const section_data = config_provider.getSection(section)
57
+ if (section_data) {
58
+ all_sections[section] = section_data
59
+ }
60
+ }
61
+ }
62
+
63
+ set_sections(all_sections)
64
+ }
65
+
66
+ useEffect(() => {
67
+ load_config()
68
+ }, [config_provider])
69
+
70
+ /**
71
+ * Start editing a config value
72
+ */
73
+ const start_edit = (section: string, key: string) => {
74
+ const value = config_provider.get(section, key) || ''
75
+ set_editing({ section, key })
76
+ set_edit_value(value)
77
+ }
78
+
79
+ /**
80
+ * Cancel editing
81
+ */
82
+ const cancel_edit = () => {
83
+ set_editing(null)
84
+ set_edit_value('')
85
+ }
86
+
87
+ /**
88
+ * Save edited value
89
+ */
90
+ const save_edit = () => {
91
+ if (editing) {
92
+ config_provider.set(editing.section, editing.key, edit_value)
93
+ config_provider.save()
94
+ load_config()
95
+ set_editing(null)
96
+ set_edit_value('')
97
+ if (on_update) {
98
+ on_update()
99
+ }
100
+ }
101
+ }
102
+
103
+ return (
104
+ <div className={cn('cls_config_viewer space-y-4', className)}>
105
+ {Object.keys(sections).length === 0 ? (
106
+ <div className="p-4 text-center text-muted-foreground">
107
+ No configuration sections found
108
+ </div>
109
+ ) : (
110
+ Object.entries(sections).map(([section_name, section_data]) => (
111
+ <div key={section_name} className="cls_config_section border rounded-lg p-4">
112
+ <h3 className="text-lg font-semibold mb-3">{section_name}</h3>
113
+ <div className="space-y-2">
114
+ {Object.entries(section_data).map(([key, value]) => {
115
+ const is_editing = editing?.section === section_name && editing?.key === key
116
+
117
+ return (
118
+ <div key={key} className="cls_config_item flex items-center gap-2">
119
+ <div className="flex-1">
120
+ <div className="text-sm font-medium text-muted-foreground">{key}</div>
121
+ {is_editing ? (
122
+ <div className="flex items-center gap-2 mt-1">
123
+ <input
124
+ type="text"
125
+ value={edit_value}
126
+ onChange={(e) => set_edit_value(e.target.value)}
127
+ className="flex-1 px-2 py-1 border rounded text-sm"
128
+ autoFocus
129
+ />
130
+ <button
131
+ onClick={save_edit}
132
+ className="text-green-600 hover:text-green-700"
133
+ aria-label="Save"
134
+ >
135
+ <CheckCircle2 size={20} />
136
+ </button>
137
+ <button
138
+ onClick={cancel_edit}
139
+ className="text-red-600 hover:text-red-700"
140
+ aria-label="Cancel"
141
+ >
142
+ <XCircle size={20} />
143
+ </button>
144
+ </div>
145
+ ) : (
146
+ <div className="flex items-center gap-2 mt-1">
147
+ <div className="flex-1 px-2 py-1 bg-muted rounded text-sm">
148
+ {value || '(empty)'}
149
+ </div>
150
+ <button
151
+ onClick={() => start_edit(section_name, key)}
152
+ className="text-blue-600 hover:text-blue-700"
153
+ aria-label="Edit"
154
+ >
155
+ <Pencil size={18} />
156
+ </button>
157
+ </div>
158
+ )}
159
+ </div>
160
+ </div>
161
+ )
162
+ })}
163
+ </div>
164
+ </div>
165
+ ))
166
+ )}
167
+ </div>
168
+ )
169
+ }
170
+
@@ -0,0 +1,59 @@
1
+ // Storybook stories for the ExampleComponent
2
+ // Demonstrates how to create stories for components
3
+
4
+ import type { Meta, StoryObj } from '@storybook/react'
5
+ import { ExampleComponent } from './example_component'
6
+
7
+ /**
8
+ * Meta configuration for ExampleComponent stories
9
+ */
10
+ const meta: Meta<typeof ExampleComponent> = {
11
+ title: 'Components/ExampleComponent',
12
+ component: ExampleComponent,
13
+ parameters: {
14
+ layout: 'centered',
15
+ },
16
+ tags: ['autodocs'],
17
+ argTypes: {
18
+ title: {
19
+ control: 'text',
20
+ description: 'The title to display',
21
+ },
22
+ className: {
23
+ control: 'text',
24
+ description: 'Additional CSS classes',
25
+ },
26
+ },
27
+ }
28
+
29
+ export default meta
30
+ type Story = StoryObj<typeof ExampleComponent>
31
+
32
+ /**
33
+ * Default story for ExampleComponent
34
+ */
35
+ export const Default: Story = {
36
+ args: {
37
+ title: 'Example Component',
38
+ },
39
+ }
40
+
41
+ /**
42
+ * Story with custom title
43
+ */
44
+ export const CustomTitle: Story = {
45
+ args: {
46
+ title: 'Custom Title Example',
47
+ },
48
+ }
49
+
50
+ /**
51
+ * Story with custom styling
52
+ */
53
+ export const CustomStyling: Story = {
54
+ args: {
55
+ title: 'Styled Component',
56
+ className: 'bg-blue-100 border-blue-500',
57
+ },
58
+ }
59
+
@@ -0,0 +1,39 @@
1
+ // Example component for the config management library
2
+ // This is a placeholder component to demonstrate the setup
3
+
4
+ import React from 'react'
5
+ import { cn } from '@/lib/utils'
6
+
7
+ /**
8
+ * Example component props interface
9
+ */
10
+ export interface ExampleComponentProps {
11
+ /**
12
+ * The title to display
13
+ */
14
+ title?: string
15
+ /**
16
+ * Additional CSS classes
17
+ */
18
+ className?: string
19
+ }
20
+
21
+ /**
22
+ * Example component for demonstrating the component library setup
23
+ * @param props - Component props
24
+ * @returns React component
25
+ */
26
+ export const ExampleComponent: React.FC<ExampleComponentProps> = ({
27
+ title = 'Example Component',
28
+ className,
29
+ }) => {
30
+ return (
31
+ <div className={cn('cls_example_component p-4 border rounded-lg', className)}>
32
+ <h2 className="text-xl font-semibold">{title}</h2>
33
+ <p className="text-muted-foreground mt-2">
34
+ This is an example component for the config management library.
35
+ </p>
36
+ </div>
37
+ )
38
+ }
39
+
@@ -0,0 +1,9 @@
1
+ // Component exports
2
+ // Export all components from this file for easy importing
3
+
4
+ export { ExampleComponent } from './example_component'
5
+ export { ConfigViewer } from './config_viewer'
6
+ export { ConfigEditor } from './config_editor'
7
+ export type { ConfigViewerProps } from './config_viewer'
8
+ export type { ConfigEditorProps } from './config_editor'
9
+
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ // Main entry point for the component library
2
+ // Export all components and utilities from this file
3
+
4
+ export * from './components'
5
+ export * from './lib/utils'
6
+ export * from './lib'
7
+