@raystack/chronicle 0.1.0-canary.e11f924

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.
Files changed (104) hide show
  1. package/bin/chronicle.js +2 -0
  2. package/dist/cli/index.js +9788 -0
  3. package/next.config.mjs +10 -0
  4. package/package.json +62 -0
  5. package/source.config.ts +50 -0
  6. package/src/app/[[...slug]]/layout.tsx +15 -0
  7. package/src/app/[[...slug]]/page.tsx +57 -0
  8. package/src/app/api/apis-proxy/route.ts +59 -0
  9. package/src/app/api/health/route.ts +3 -0
  10. package/src/app/api/search/route.ts +90 -0
  11. package/src/app/apis/[[...slug]]/layout.module.css +22 -0
  12. package/src/app/apis/[[...slug]]/layout.tsx +26 -0
  13. package/src/app/apis/[[...slug]]/page.tsx +57 -0
  14. package/src/app/layout.tsx +26 -0
  15. package/src/app/llms-full.txt/route.ts +18 -0
  16. package/src/app/llms.txt/route.ts +15 -0
  17. package/src/app/providers.tsx +8 -0
  18. package/src/cli/commands/build.ts +33 -0
  19. package/src/cli/commands/dev.ts +34 -0
  20. package/src/cli/commands/init.ts +58 -0
  21. package/src/cli/commands/serve.ts +54 -0
  22. package/src/cli/commands/start.ts +34 -0
  23. package/src/cli/index.ts +21 -0
  24. package/src/cli/utils/config.ts +43 -0
  25. package/src/cli/utils/index.ts +2 -0
  26. package/src/cli/utils/process.ts +7 -0
  27. package/src/components/api/code-snippets.module.css +7 -0
  28. package/src/components/api/code-snippets.tsx +76 -0
  29. package/src/components/api/endpoint-page.module.css +58 -0
  30. package/src/components/api/endpoint-page.tsx +283 -0
  31. package/src/components/api/field-row.module.css +126 -0
  32. package/src/components/api/field-row.tsx +204 -0
  33. package/src/components/api/field-section.module.css +24 -0
  34. package/src/components/api/field-section.tsx +100 -0
  35. package/src/components/api/index.ts +8 -0
  36. package/src/components/api/json-editor.module.css +9 -0
  37. package/src/components/api/json-editor.tsx +61 -0
  38. package/src/components/api/key-value-editor.module.css +13 -0
  39. package/src/components/api/key-value-editor.tsx +62 -0
  40. package/src/components/api/method-badge.module.css +4 -0
  41. package/src/components/api/method-badge.tsx +29 -0
  42. package/src/components/api/response-panel.module.css +8 -0
  43. package/src/components/api/response-panel.tsx +44 -0
  44. package/src/components/common/breadcrumb.tsx +3 -0
  45. package/src/components/common/button.tsx +3 -0
  46. package/src/components/common/callout.module.css +7 -0
  47. package/src/components/common/callout.tsx +27 -0
  48. package/src/components/common/code-block.tsx +3 -0
  49. package/src/components/common/dialog.tsx +3 -0
  50. package/src/components/common/index.ts +10 -0
  51. package/src/components/common/input-field.tsx +3 -0
  52. package/src/components/common/sidebar.tsx +3 -0
  53. package/src/components/common/switch.tsx +3 -0
  54. package/src/components/common/table.tsx +3 -0
  55. package/src/components/common/tabs.tsx +3 -0
  56. package/src/components/mdx/code.module.css +42 -0
  57. package/src/components/mdx/code.tsx +27 -0
  58. package/src/components/mdx/details.module.css +37 -0
  59. package/src/components/mdx/details.tsx +18 -0
  60. package/src/components/mdx/image.tsx +38 -0
  61. package/src/components/mdx/index.tsx +35 -0
  62. package/src/components/mdx/link.tsx +38 -0
  63. package/src/components/mdx/mermaid.module.css +9 -0
  64. package/src/components/mdx/mermaid.tsx +37 -0
  65. package/src/components/mdx/paragraph.module.css +8 -0
  66. package/src/components/mdx/paragraph.tsx +19 -0
  67. package/src/components/mdx/table.tsx +40 -0
  68. package/src/components/ui/breadcrumbs.tsx +72 -0
  69. package/src/components/ui/client-theme-switcher.tsx +18 -0
  70. package/src/components/ui/footer.module.css +27 -0
  71. package/src/components/ui/footer.tsx +30 -0
  72. package/src/components/ui/search.module.css +104 -0
  73. package/src/components/ui/search.tsx +202 -0
  74. package/src/lib/api-routes.ts +120 -0
  75. package/src/lib/config.ts +47 -0
  76. package/src/lib/get-llm-text.ts +10 -0
  77. package/src/lib/index.ts +2 -0
  78. package/src/lib/openapi.ts +188 -0
  79. package/src/lib/remark-unused-directives.ts +30 -0
  80. package/src/lib/schema.ts +99 -0
  81. package/src/lib/snippet-generators.ts +87 -0
  82. package/src/lib/source.ts +67 -0
  83. package/src/themes/default/Layout.module.css +81 -0
  84. package/src/themes/default/Layout.tsx +133 -0
  85. package/src/themes/default/Page.module.css +46 -0
  86. package/src/themes/default/Page.tsx +21 -0
  87. package/src/themes/default/Toc.module.css +48 -0
  88. package/src/themes/default/Toc.tsx +66 -0
  89. package/src/themes/default/font.ts +6 -0
  90. package/src/themes/default/index.ts +13 -0
  91. package/src/themes/paper/ChapterNav.module.css +71 -0
  92. package/src/themes/paper/ChapterNav.tsx +96 -0
  93. package/src/themes/paper/Layout.module.css +33 -0
  94. package/src/themes/paper/Layout.tsx +25 -0
  95. package/src/themes/paper/Page.module.css +174 -0
  96. package/src/themes/paper/Page.tsx +107 -0
  97. package/src/themes/paper/ReadingProgress.module.css +132 -0
  98. package/src/themes/paper/ReadingProgress.tsx +294 -0
  99. package/src/themes/paper/index.ts +8 -0
  100. package/src/themes/registry.ts +14 -0
  101. package/src/types/config.ts +69 -0
  102. package/src/types/content.ts +35 -0
  103. package/src/types/index.ts +3 -0
  104. package/src/types/theme.ts +22 -0
@@ -0,0 +1,126 @@
1
+ .row {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--rs-space-2);
5
+ padding: var(--rs-space-4) 0;
6
+ }
7
+
8
+ .row + .row {
9
+ border-top: 1px solid var(--rs-color-border-base-primary);
10
+ }
11
+
12
+ .main {
13
+ gap: var(--rs-space-2);
14
+ }
15
+
16
+ .badges {
17
+ gap: var(--rs-space-3);
18
+ flex-wrap: wrap;
19
+ }
20
+
21
+ .name {
22
+ font-family: monospace;
23
+ font-size: 13px;
24
+ color: var(--rs-color-foreground-base-primary);
25
+ }
26
+
27
+ .type {
28
+ font-family: monospace;
29
+ font-size: 12px;
30
+ padding: 1px var(--rs-space-2);
31
+ border-radius: 4px;
32
+ background: var(--rs-color-background-neutral-secondary);
33
+ color: var(--rs-color-foreground-base-secondary);
34
+ }
35
+
36
+ .location {
37
+ font-size: 12px;
38
+ padding: 1px var(--rs-space-2);
39
+ border-radius: 4px;
40
+ background: var(--rs-color-background-neutral-secondary);
41
+ color: var(--rs-color-foreground-base-secondary);
42
+ }
43
+
44
+ .required {
45
+ font-size: 11px;
46
+ padding: 1px var(--rs-space-2);
47
+ border-radius: 4px;
48
+ background: var(--rs-color-background-danger-primary);
49
+ color: var(--rs-color-foreground-danger-primary);
50
+ }
51
+
52
+ .description {
53
+ color: var(--rs-color-foreground-base-secondary);
54
+ font-size: 13px;
55
+ }
56
+
57
+ .example {
58
+ color: var(--rs-color-foreground-base-secondary);
59
+ font-size: 12px;
60
+ }
61
+
62
+ .example code {
63
+ font-family: monospace;
64
+ background: var(--rs-color-background-neutral-secondary);
65
+ padding: 1px var(--rs-space-2);
66
+ border-radius: 3px;
67
+ }
68
+
69
+ .accordion {
70
+ border: none;
71
+ }
72
+
73
+ .accordion :global([class*="accordion-header"]) {
74
+ margin: 0;
75
+ }
76
+
77
+ .accordion button {
78
+ min-height: unset;
79
+ padding: 0;
80
+ border: none;
81
+ background: transparent;
82
+ box-shadow: none;
83
+ }
84
+
85
+ .accordion button:hover,
86
+ .accordion button:focus-visible {
87
+ background: transparent;
88
+ }
89
+
90
+ .accordion :global([class*="accordion-content-inner"]) {
91
+ padding: var(--rs-space-3) 0 0 0;
92
+ border: none;
93
+ box-shadow: none;
94
+ }
95
+
96
+ .children {
97
+ display: flex;
98
+ flex-direction: column;
99
+ padding-left: var(--rs-space-5);
100
+ width: 100%;
101
+ }
102
+
103
+ .trigger {
104
+ color: var(--rs-color-foreground-base-secondary);
105
+ }
106
+
107
+ .fieldInfo {
108
+ flex: 1;
109
+ min-width: 0;
110
+ }
111
+
112
+ .fieldInput {
113
+ flex: 1;
114
+ min-width: 0;
115
+ }
116
+
117
+ .arrayItems {
118
+ gap: var(--rs-space-3);
119
+ }
120
+
121
+ .arrayItem {
122
+ align-items: center;
123
+ border: 1px solid var(--rs-color-border-base-primary);
124
+ border-radius: 8px;
125
+ padding: var(--rs-space-3) var(--rs-space-4);
126
+ }
@@ -0,0 +1,204 @@
1
+ 'use client'
2
+
3
+ import { Flex, Text, Accordion, InputField, Switch, Select, IconButton } from '@raystack/apsara'
4
+ import { TrashIcon, PlusIcon } from '@heroicons/react/24/outline'
5
+ import type { SchemaField } from '@/lib/schema'
6
+ import styles from './field-row.module.css'
7
+
8
+ interface FieldRowProps {
9
+ field: SchemaField
10
+ location?: string
11
+ editable?: boolean
12
+ value?: unknown
13
+ onChange?: (name: string, value: unknown) => void
14
+ }
15
+
16
+ export function FieldRow({ field, location, editable, value, onChange }: FieldRowProps) {
17
+ const hasChildren = field.children && field.children.length > 0
18
+ const isArray = field.type.endsWith('[]')
19
+
20
+ const label = (
21
+ <Flex align="center" className={styles.badges}>
22
+ <Text size={2} className={styles.name}>{field.name}</Text>
23
+ <Text size={1} className={styles.type}>{field.type}</Text>
24
+ {location && <Text size={1} className={styles.location}>{location}</Text>}
25
+ {field.required && <Text size={1} className={styles.required}>required</Text>}
26
+ </Flex>
27
+ )
28
+
29
+ if (hasChildren && !isArray) {
30
+ const objValue = (value ?? {}) as Record<string, unknown>
31
+ return (
32
+ <div className={styles.row}>
33
+ <Accordion collapsible className={styles.accordion}>
34
+ <Accordion.Item value={field.name}>
35
+ <Accordion.Trigger className={styles.trigger}>{label}</Accordion.Trigger>
36
+ <Accordion.Content>
37
+ <div className={styles.children}>
38
+ {field.children!.map((child) => (
39
+ <FieldRow
40
+ key={child.name}
41
+ field={child}
42
+ editable={editable}
43
+ value={objValue[child.name]}
44
+ onChange={editable ? (name, val) => {
45
+ onChange?.(field.name, { ...objValue, [name]: val })
46
+ } : undefined}
47
+ />
48
+ ))}
49
+ </div>
50
+ </Accordion.Content>
51
+ </Accordion.Item>
52
+ </Accordion>
53
+ </div>
54
+ )
55
+ }
56
+
57
+ if (isArray && editable) {
58
+ const items = (Array.isArray(value) ? value : []) as unknown[]
59
+ const itemChildren = field.children
60
+
61
+ return (
62
+ <div className={styles.row}>
63
+ <Flex direction="column" className={styles.main}>
64
+ <Flex align="center" justify="between">
65
+ {label}
66
+ <IconButton size={1} onClick={() => {
67
+ const newItem = itemChildren ? {} : ''
68
+ onChange?.(field.name, [...items, newItem])
69
+ }}>
70
+ <PlusIcon width={14} height={14} />
71
+ </IconButton>
72
+ </Flex>
73
+ {field.description && <Text size={2} className={styles.description}>{field.description}</Text>}
74
+ <Flex direction="column" className={styles.arrayItems}>
75
+ {items.map((item, i) => (
76
+ <Flex key={i} align="start" gap="small" className={styles.arrayItem}>
77
+ {itemChildren ? (
78
+ <Flex direction="column" className={styles.children}>
79
+ {itemChildren.map((child) => (
80
+ <FieldRow
81
+ key={child.name}
82
+ field={child}
83
+ editable
84
+ value={(item as Record<string, unknown>)?.[child.name]}
85
+ onChange={(name, val) => {
86
+ const updated = [...items]
87
+ updated[i] = { ...(updated[i] as Record<string, unknown>), [name]: val }
88
+ onChange?.(field.name, updated)
89
+ }}
90
+ />
91
+ ))}
92
+ </Flex>
93
+ ) : (
94
+ <EditableInput
95
+ field={{
96
+ ...field,
97
+ name: `${field.name}[${i}]`,
98
+ type: field.type.replace('[]', ''),
99
+ }}
100
+ value={item}
101
+ onChange={(_, val) => {
102
+ const updated = [...items]
103
+ updated[i] = val
104
+ onChange?.(field.name, updated)
105
+ }}
106
+ />
107
+ )}
108
+ <IconButton size={1} onClick={() => {
109
+ const updated = items.filter((_, j) => j !== i)
110
+ onChange?.(field.name, updated)
111
+ }}>
112
+ <TrashIcon width={14} height={14} />
113
+ </IconButton>
114
+ </Flex>
115
+ ))}
116
+ </Flex>
117
+ </Flex>
118
+ </div>
119
+ )
120
+ }
121
+
122
+ // Leaf field — inline layout
123
+ return (
124
+ <div className={styles.row}>
125
+ <Flex align="center" gap="medium">
126
+ <Flex direction="column" gap="extra-small" className={styles.fieldInfo}>
127
+ {label}
128
+ {field.description && <Text size={2} className={styles.description}>{field.description}</Text>}
129
+ </Flex>
130
+ {editable ? (
131
+ <div className={styles.fieldInput}>
132
+ <EditableInput field={field} value={value} onChange={onChange} />
133
+ </div>
134
+ ) : (
135
+ field.default !== undefined && (
136
+ <Text size={1} className={styles.example}>
137
+ Default: <code>{JSON.stringify(field.default)}</code>
138
+ </Text>
139
+ )
140
+ )}
141
+ </Flex>
142
+ </div>
143
+ )
144
+ }
145
+
146
+ function EditableInput({
147
+ field,
148
+ value,
149
+ onChange,
150
+ }: {
151
+ field: SchemaField
152
+ value: unknown
153
+ onChange?: (name: string, value: unknown) => void
154
+ }) {
155
+ if (field.enum) {
156
+ const enumMap = new Map(field.enum.map((opt) => [String(opt), opt]))
157
+ return (
158
+ <Select value={String(value ?? '')} onValueChange={(v) => onChange?.(field.name, enumMap.get(v) ?? v)}>
159
+ <Select.Trigger size="small">
160
+ <Select.Value placeholder={`Select ${field.name}`} />
161
+ </Select.Trigger>
162
+ <Select.Content>
163
+ {field.enum.map((opt) => (
164
+ <Select.Item key={String(opt)} value={String(opt)}>
165
+ {String(opt)}
166
+ </Select.Item>
167
+ ))}
168
+ </Select.Content>
169
+ </Select>
170
+ )
171
+ }
172
+
173
+ const baseType = field.type.replace('[]', '').replace(/\(.*\)/, '')
174
+
175
+ if (baseType === 'boolean') {
176
+ return (
177
+ <Switch
178
+ checked={Boolean(value)}
179
+ onCheckedChange={(checked) => onChange?.(field.name, checked)}
180
+ />
181
+ )
182
+ }
183
+
184
+ if (baseType === 'integer' || baseType === 'number') {
185
+ return (
186
+ <InputField
187
+ size="small"
188
+ type="number"
189
+ placeholder={field.description ?? field.name}
190
+ value={String(value ?? '')}
191
+ onChange={(e) => onChange?.(field.name, Number(e.target.value))}
192
+ />
193
+ )
194
+ }
195
+
196
+ return (
197
+ <InputField
198
+ size="small"
199
+ placeholder={field.description ?? field.name}
200
+ value={String(value ?? '')}
201
+ onChange={(e) => onChange?.(field.name, e.target.value)}
202
+ />
203
+ )
204
+ }
@@ -0,0 +1,24 @@
1
+ .header {
2
+ padding-bottom: var(--rs-space-3);
3
+ }
4
+
5
+ .label {
6
+ font-family: monospace;
7
+ color: var(--rs-color-foreground-base-secondary);
8
+ font-size: 13px;
9
+ }
10
+
11
+ .separator {
12
+ height: 1px;
13
+ background: var(--rs-color-border-base-primary);
14
+ }
15
+
16
+ .tabs {
17
+ margin-top: var(--rs-space-3);
18
+ }
19
+
20
+ .noFields {
21
+ color: var(--rs-color-foreground-base-secondary);
22
+ padding: var(--rs-space-5);
23
+ }
24
+
@@ -0,0 +1,100 @@
1
+ 'use client'
2
+
3
+ import { Flex, Text, Tabs, CodeBlock } from '@raystack/apsara'
4
+ import type { SchemaField } from '@/lib/schema'
5
+ import { FieldRow } from './field-row'
6
+ import { JsonEditor } from './json-editor'
7
+ import styles from './field-section.module.css'
8
+
9
+ interface FieldSectionProps {
10
+ title: string
11
+ label?: string
12
+ fields: SchemaField[]
13
+ locations?: Record<string, string>
14
+ jsonExample?: string
15
+ editableJson?: boolean
16
+ onJsonChange?: (value: string) => void
17
+ alwaysShow?: boolean
18
+ editable?: boolean
19
+ values?: Record<string, unknown>
20
+ onValuesChange?: (values: Record<string, unknown>) => void
21
+ children?: React.ReactNode
22
+ }
23
+
24
+ export function FieldSection({
25
+ title, label, fields, locations, jsonExample,
26
+ editableJson, onJsonChange, alwaysShow,
27
+ editable, values, onValuesChange, children,
28
+ }: FieldSectionProps) {
29
+ if (fields.length === 0 && !children && !alwaysShow) return null
30
+
31
+ const fieldsContent = fields.length > 0 ? (
32
+ <Flex direction="column">
33
+ {fields.map((field) => (
34
+ <FieldRow
35
+ key={field.name}
36
+ field={field}
37
+ location={locations?.[field.name]}
38
+ editable={editable}
39
+ value={values?.[field.name]}
40
+ onChange={editable ? (name, val) => {
41
+ onValuesChange?.({ ...values, [name]: val })
42
+ } : undefined}
43
+ />
44
+ ))}
45
+ </Flex>
46
+ ) : !children ? (
47
+ <Text size={2} className={styles.noFields}>No fields defined</Text>
48
+ ) : null
49
+
50
+ if (jsonExample !== undefined || alwaysShow) {
51
+ return (
52
+ <Flex direction="column">
53
+ <Flex align="center" justify="between" className={styles.header}>
54
+ <Text size={4} weight="medium">{title}</Text>
55
+ {label && <Text size={2} className={styles.label}>{label}</Text>}
56
+ </Flex>
57
+ <div className={styles.separator} />
58
+ <Tabs defaultValue="fields" className={styles.tabs}>
59
+ <Tabs.List>
60
+ <Tabs.Trigger value="fields">Fields</Tabs.Trigger>
61
+ <Tabs.Trigger value="json">JSON</Tabs.Trigger>
62
+ </Tabs.List>
63
+ <Tabs.Content value="fields">
64
+ {fieldsContent}
65
+ {children}
66
+ </Tabs.Content>
67
+ <Tabs.Content value="json">
68
+ {editableJson ? (
69
+ <JsonEditor
70
+ value={jsonExample ?? '{}'}
71
+ onChange={onJsonChange}
72
+ />
73
+ ) : (
74
+ <CodeBlock>
75
+ <CodeBlock.Header>
76
+ <CodeBlock.CopyButton />
77
+ </CodeBlock.Header>
78
+ <CodeBlock.Content>
79
+ <CodeBlock.Code language="json">{jsonExample ?? '{}'}</CodeBlock.Code>
80
+ </CodeBlock.Content>
81
+ </CodeBlock>
82
+ )}
83
+ </Tabs.Content>
84
+ </Tabs>
85
+ </Flex>
86
+ )
87
+ }
88
+
89
+ return (
90
+ <Flex direction="column">
91
+ <Flex align="center" justify="between" className={styles.header}>
92
+ <Text size={4} weight="medium">{title}</Text>
93
+ {label && <Text size={2} className={styles.label}>{label}</Text>}
94
+ </Flex>
95
+ <div className={styles.separator} />
96
+ {fieldsContent}
97
+ {children}
98
+ </Flex>
99
+ )
100
+ }
@@ -0,0 +1,8 @@
1
+ export { EndpointPage } from './endpoint-page'
2
+ export { MethodBadge } from './method-badge'
3
+ export { FieldSection } from './field-section'
4
+ export { FieldRow } from './field-row'
5
+ export { CodeSnippets } from './code-snippets'
6
+ export { ResponsePanel } from './response-panel'
7
+ export { JsonEditor } from './json-editor'
8
+ export { KeyValueEditor } from './key-value-editor'
@@ -0,0 +1,9 @@
1
+ .editor {
2
+ border: 1px solid var(--rs-color-border-base-primary);
3
+ border-radius: 8px;
4
+ overflow: hidden;
5
+ }
6
+
7
+ .editor .cm-editor {
8
+ font-size: var(--rs-font-size-small);
9
+ }
@@ -0,0 +1,61 @@
1
+ 'use client'
2
+
3
+ import { useRef, useEffect } from 'react'
4
+ import { useTheme } from '@raystack/apsara'
5
+ import { EditorView, basicSetup } from 'codemirror'
6
+ import { EditorState } from '@codemirror/state'
7
+ import { json } from '@codemirror/lang-json'
8
+ import { oneDark } from '@codemirror/theme-one-dark'
9
+ import styles from './json-editor.module.css'
10
+
11
+ interface JsonEditorProps {
12
+ value: string
13
+ onChange?: (value: string) => void
14
+ readOnly?: boolean
15
+ }
16
+
17
+ export function JsonEditor({ value, onChange, readOnly }: JsonEditorProps) {
18
+ const containerRef = useRef<HTMLDivElement>(null)
19
+ const viewRef = useRef<EditorView | null>(null)
20
+ const { theme } = useTheme()
21
+
22
+ const isDark = theme === 'dark'
23
+
24
+ useEffect(() => {
25
+ if (!containerRef.current) return
26
+
27
+ const extensions = [
28
+ basicSetup,
29
+ json(),
30
+ EditorView.lineWrapping,
31
+ ...(isDark ? [oneDark] : []),
32
+ ...(readOnly ? [EditorState.readOnly.of(true)] : []),
33
+ ...(onChange
34
+ ? [EditorView.updateListener.of((update) => {
35
+ if (update.docChanged) {
36
+ onChange(update.state.doc.toString())
37
+ }
38
+ })]
39
+ : []),
40
+ ]
41
+
42
+ const state = EditorState.create({ doc: value, extensions })
43
+ const view = new EditorView({ state, parent: containerRef.current })
44
+ viewRef.current = view
45
+
46
+ return () => view.destroy()
47
+ }, [isDark, readOnly, onChange])
48
+
49
+ useEffect(() => {
50
+ const view = viewRef.current
51
+ if (!view) return
52
+ const current = view.state.doc.toString()
53
+ if (value !== current) {
54
+ view.dispatch({
55
+ changes: { from: 0, to: current.length, insert: value },
56
+ })
57
+ }
58
+ }, [value])
59
+
60
+ return <div ref={containerRef} className={styles.editor} />
61
+ }
@@ -0,0 +1,13 @@
1
+ .editor {
2
+ padding: var(--rs-space-3) 0;
3
+ }
4
+
5
+ .row {
6
+ width: 100%;
7
+ }
8
+
9
+ .input {
10
+ flex: 1;
11
+ min-width: 0;
12
+ }
13
+
@@ -0,0 +1,62 @@
1
+ 'use client'
2
+
3
+ import { Flex, InputField, IconButton, Button } from '@raystack/apsara'
4
+ import { TrashIcon, PlusIcon } from '@heroicons/react/24/outline'
5
+ import styles from './key-value-editor.module.css'
6
+
7
+ export interface KeyValueEntry {
8
+ key: string
9
+ value: string
10
+ }
11
+
12
+ interface KeyValueEditorProps {
13
+ entries: KeyValueEntry[]
14
+ onChange: (entries: KeyValueEntry[]) => void
15
+ }
16
+
17
+ export function KeyValueEditor({ entries, onChange }: KeyValueEditorProps) {
18
+ const updateEntry = (index: number, field: 'key' | 'value', val: string) => {
19
+ const updated = [...entries]
20
+ updated[index] = { ...updated[index], [field]: val }
21
+ onChange(updated)
22
+ }
23
+
24
+ const removeEntry = (index: number) => {
25
+ onChange(entries.filter((_, i) => i !== index))
26
+ }
27
+
28
+ const addEntry = () => {
29
+ onChange([...entries, { key: '', value: '' }])
30
+ }
31
+
32
+ return (
33
+ <Flex direction="column" gap="small" className={styles.editor}>
34
+ {entries.map((entry, i) => (
35
+ <Flex key={i} align="center" gap="small" className={styles.row}>
36
+ <div className={styles.input}>
37
+ <InputField
38
+ size="small"
39
+ placeholder="Header name"
40
+ value={entry.key}
41
+ onChange={(e) => updateEntry(i, 'key', e.target.value)}
42
+ />
43
+ </div>
44
+ <div className={styles.input}>
45
+ <InputField
46
+ size="small"
47
+ placeholder="Value"
48
+ value={entry.value}
49
+ onChange={(e) => updateEntry(i, 'value', e.target.value)}
50
+ />
51
+ </div>
52
+ <IconButton size={1} aria-label={`Delete ${entry.key || 'entry'}`} onClick={() => removeEntry(i)}>
53
+ <TrashIcon width={14} height={14} />
54
+ </IconButton>
55
+ </Flex>
56
+ ))}
57
+ <Button variant="ghost" size="small" onClick={addEntry}>
58
+ <PlusIcon width={14} height={14} /> Add header
59
+ </Button>
60
+ </Flex>
61
+ )
62
+ }
@@ -0,0 +1,4 @@
1
+ .badge {
2
+ font-family: monospace;
3
+ letter-spacing: 0.5px;
4
+ }
@@ -0,0 +1,29 @@
1
+ 'use client'
2
+
3
+ import { Badge } from '@raystack/apsara'
4
+ import styles from './method-badge.module.css'
5
+
6
+ type BadgeVariant = 'accent' | 'danger' | 'success' | 'neutral' | 'warning' | 'gradient'
7
+
8
+ const methodVariants: Record<string, BadgeVariant> = {
9
+ GET: 'accent',
10
+ POST: 'success',
11
+ PUT: 'warning',
12
+ DELETE: 'danger',
13
+ PATCH: 'neutral',
14
+ }
15
+
16
+ interface MethodBadgeProps {
17
+ method: string
18
+ size?: 'micro' | 'small' | 'regular'
19
+ }
20
+
21
+ export function MethodBadge({ method, size = 'small' }: MethodBadgeProps) {
22
+ const variant = methodVariants[method.toUpperCase()] ?? 'neutral'
23
+
24
+ return (
25
+ <Badge variant={variant} size={size} className={styles.badge}>
26
+ {method.toUpperCase()}
27
+ </Badge>
28
+ )
29
+ }
@@ -0,0 +1,8 @@
1
+ .panel {
2
+ width: 100%;
3
+ }
4
+
5
+ /* stylelint-disable-next-line selector-pseudo-class-no-unknown */
6
+ .panel :global([class*="code-block-module_header"]) {
7
+ justify-content: space-between;
8
+ }