@oslokommune/punkt-react 13.6.16 → 13.8.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/index.d.ts +58 -76
  3. package/dist/punkt-react.es.js +5331 -35936
  4. package/dist/punkt-react.umd.js +422 -552
  5. package/package.json +11 -15
  6. package/src/components/accordion/Accordion.test.tsx +1 -1
  7. package/src/components/accordion/Accordion.tsx +1 -1
  8. package/src/components/accordion/AccordionItem.tsx +6 -5
  9. package/src/components/alert/Alert.tsx +2 -1
  10. package/src/components/breadcrumbs/Breadcrumbs.test.tsx +16 -4
  11. package/src/components/breadcrumbs/Breadcrumbs.tsx +3 -2
  12. package/src/components/button/Button.tsx +3 -2
  13. package/src/components/checkbox/Checkbox.tsx +4 -3
  14. package/src/components/footer/Footer.tsx +6 -5
  15. package/src/components/footerSimple/FooterSimple.tsx +4 -3
  16. package/src/components/icon/Icon.test.tsx +6 -19
  17. package/src/components/index.ts +1 -2
  18. package/src/components/input/Input.tsx +4 -3
  19. package/src/components/radio/RadioButton.tsx +3 -2
  20. package/src/components/searchinput/SearchInput.tsx +3 -3
  21. package/src/components/stepper/Stepper.tsx +6 -6
  22. package/src/components/table/Table.tsx +2 -1
  23. package/src/components/table/TableBody.tsx +2 -1
  24. package/src/components/table/TableData.tsx +2 -1
  25. package/src/components/table/TableDataCell.tsx +2 -1
  26. package/src/components/table/TableHeader.tsx +2 -1
  27. package/src/components/table/TableHeaderCell.tsx +2 -1
  28. package/src/components/table/TableRow.tsx +2 -1
  29. package/src/components/tabs/TabItem.tsx +77 -0
  30. package/src/components/tabs/Tabs.test.tsx +393 -1
  31. package/src/components/tabs/Tabs.tsx +86 -65
  32. package/src/components/tag/Tag.tsx +2 -1
  33. package/src/components/preview/Preview.tsx +0 -274
  34. package/src/components/preview/PreviewCode.tsx +0 -118
  35. package/src/components/preview/PreviewPropEditor.tsx +0 -266
  36. package/src/components/preview/PreviewSpecs.tsx +0 -125
  37. package/src/components/preview/PreviewWithJson.tsx +0 -519
  38. package/src/components/preview/preview-types.ts +0 -42
  39. package/src/components/preview/preview-utils.ts +0 -226
  40. package/src/components/preview/previewJson/accordion.json +0 -84
  41. package/src/components/preview/previewJson/alert.json +0 -27
  42. package/src/components/preview/previewJson/backlink.json +0 -14
  43. package/src/components/preview/previewJson/breadcrumbs.json +0 -17
  44. package/src/components/preview/previewJson/button.json +0 -28
  45. package/src/components/preview/previewJson/card.json +0 -41
  46. package/src/components/preview/previewJson/checkbox.json +0 -21
  47. package/src/components/preview/previewJson/combobox.json +0 -24
  48. package/src/components/preview/previewJson/consent.json +0 -14
  49. package/src/components/preview/previewJson/datepicker.json +0 -14
  50. package/src/components/preview/previewJson/footer-simple.json +0 -17
  51. package/src/components/preview/previewJson/footer.json +0 -29
  52. package/src/components/preview/previewJson/header.json +0 -34
  53. package/src/components/preview/previewJson/icon.json +0 -13
  54. package/src/components/preview/previewJson/input-wrapper.json +0 -39
  55. package/src/components/preview/previewJson/link.json +0 -28
  56. package/src/components/preview/previewJson/linkcard.json +0 -30
  57. package/src/components/preview/previewJson/loader.json +0 -28
  58. package/src/components/preview/previewJson/messagebox.json +0 -28
  59. package/src/components/preview/previewJson/modal.json +0 -65
  60. package/src/components/preview/previewJson/progressbar.json +0 -16
  61. package/src/components/preview/previewJson/radiobutton.json +0 -21
  62. package/src/components/preview/previewJson/searchinput.json +0 -20
  63. package/src/components/preview/previewJson/select.json +0 -73
  64. package/src/components/preview/previewJson/steps.json +0 -72
  65. package/src/components/preview/previewJson/table.json +0 -130
  66. package/src/components/preview/previewJson/tabs.json +0 -33
  67. package/src/components/preview/previewJson/tag.json +0 -26
  68. package/src/components/preview/previewJson/textarea.json +0 -18
  69. package/src/components/preview/previewJson/textinput.json +0 -18
@@ -1,274 +0,0 @@
1
- 'use client'
2
-
3
- import React, { cloneElement, isValidElement, ReactNode, useRef, useState } from 'react'
4
- import reactElementToJSXString from 'react-element-to-jsx-string'
5
- import * as prettier from 'prettier/standalone'
6
- import * as parserHtml from 'prettier/parser-html'
7
- import { PrismLight as SyntaxHighlighter } from 'react-syntax-highlighter'
8
- import jsx from 'react-syntax-highlighter/dist/esm/languages/prism/jsx'
9
- import { prism } from 'react-syntax-highlighter/dist/esm/styles/prism'
10
- import { PktButton } from '../button/Button'
11
- import { PktCheckbox } from '../checkbox/Checkbox'
12
- import { PktTabs } from '../tabs/Tabs'
13
- import { PktTag } from '../tag/Tag'
14
- import { PktTextinput } from '../textinput/Textinput'
15
- import { PktPreviewSpecs } from './PreviewSpecs'
16
- import type { ISpecObject } from './preview-types'
17
- import { PktPreviewPropEditor } from './PreviewPropEditor'
18
-
19
- SyntaxHighlighter.registerLanguage('jsx', jsx)
20
-
21
- interface PreviewProps {
22
- children?: ReactNode
23
- specs: ISpecObject
24
- fullWidth?: boolean
25
- }
26
-
27
- export const PktPreview: React.FC<PreviewProps> = ({ specs, children, fullWidth }) => {
28
- const initialProps =
29
- specs.props && typeof specs.props === 'object' && !Array.isArray(specs.props)
30
- ? Object.entries(specs.props).reduce<Record<string, any>>((acc, [key, value]) => {
31
- if (
32
- typeof value === 'object' &&
33
- !Array.isArray(value) &&
34
- value.previewDefault !== undefined &&
35
- value.previewDefault !== null
36
- ) {
37
- if (value.previewDefault === 'false') {
38
- value.previewDefault = false
39
- }
40
- if (value.previewDefault === 'true') {
41
- value.previewDefault = true
42
- }
43
- acc[key] = value.previewDefault
44
- } else if (
45
- typeof value === 'object' &&
46
- !Array.isArray(value) &&
47
- value.default !== undefined &&
48
- value.default !== null
49
- ) {
50
- if (value.default === 'false') {
51
- value.default = false
52
- }
53
- if (value.default === 'true') {
54
- value.default = true
55
- }
56
- acc[key] = value.default
57
- }
58
- return acc
59
- }, {})
60
- : {}
61
- const [iteratedKey, setIteratedKey] = useState(0)
62
- const [props, setProps] = useState(initialProps)
63
- const [mode, setMode] = useState<'light' | 'dark'>('light')
64
- const [htmlContent, setHtmlContent] = useState('')
65
- const [jsxContent, setJsxContent] = useState('')
66
- const [copied, setCopied] = useState('')
67
- const [slotContent, setSlotContent] = useState('Innhold')
68
-
69
- const [tabs, setTabs] = useState([
70
- {
71
- text: 'Rediger',
72
- icon: 'adjust',
73
- active: true,
74
- },
75
- {
76
- text: 'Kode (React)',
77
- icon: 'react',
78
- active: false,
79
- },
80
- {
81
- text: `Kode (${specs.isElement ? 'Element' : 'HTML'})`,
82
- icon: 'code',
83
- active: false,
84
- },
85
- {
86
- text: 'Egenskaper',
87
- icon: 'list',
88
- active: false,
89
- },
90
- ])
91
-
92
- const changeContent = (id: number) => {
93
- if (id === 1) {
94
- fetchJsx()
95
- }
96
- if (id === 2) {
97
- fetchHtml()
98
- }
99
- setCopied('')
100
- setTabs((prevTabs) =>
101
- prevTabs.map((tab, index) => ({
102
- ...tab,
103
- active: index === id,
104
- })),
105
- )
106
- }
107
-
108
- const previewComponent = useRef<any>(null)
109
-
110
- const fetchHtml = async () => {
111
- if (specs.isElement && isValidElement(component)) {
112
- const componentType = isValidElement(component)
113
- ? (component.type as any).displayName || (component.type as any).name
114
- : specs.name
115
-
116
- const attributes = Object.entries(props)
117
- .map(([key, value]) => {
118
- if (value && !isEmptyArray(value) && !isEmptyObject(value)) {
119
- return typeof value === 'object' ? `${key}='${JSON.stringify(value)}'` : `${key}="${value}"`
120
- }
121
- })
122
- .join(' ')
123
-
124
- const customElement = await prettier.format(
125
- `<${specs.name || componentType} ${attributes}>${slotContent}</${specs.name || componentType}>`,
126
- {
127
- parser: 'html',
128
- plugins: [parserHtml as any],
129
- },
130
- )
131
-
132
- setHtmlContent(
133
- '<!-- Denne koden bør testes grundig før bruk. Spør en Punkt-utvikler om du er usikker. -->\n' + customElement,
134
- )
135
- } else {
136
- const html = await prettier.format(previewComponent.current.innerHTML, {
137
- parser: 'html',
138
- plugins: [parserHtml as any],
139
- })
140
- setHtmlContent(html.replace(/<\!--.*?-->/g, ''))
141
- }
142
- }
143
-
144
- const fetchJsx = async () => {
145
- const jsx = await reactElementToJSXString(component)
146
- setJsxContent(jsx)
147
- return Promise.resolve()
148
- }
149
-
150
- const copyToClipboard = (type: string, content: string) => {
151
- navigator.clipboard.writeText(content).then(() => {
152
- setCopied(type)
153
- console.log('Copied to clipboard', type)
154
- })
155
- }
156
-
157
- const isEmptyArray = (arr: any) => Array.isArray(arr) && arr.length === 0
158
- const isEmptyObject = (obj: any) => obj && typeof obj === 'object' && !Object.keys(obj).length
159
-
160
- const CopyButton = (type: string, content: string) => (
161
- <div className="pkt-preview__copy">
162
- {copied === type && <span className="pkt-preview__copied">Kode kopiert til utklippstavle</span>}
163
- <PktButton
164
- skin="tertiary"
165
- variant="icon-only"
166
- size="small"
167
- iconName="copy"
168
- onClick={() => copyToClipboard(type, content)}
169
- >
170
- Kopier kode
171
- </PktButton>
172
- </div>
173
- )
174
-
175
- const component =
176
- isValidElement(children) &&
177
- cloneElement(
178
- children,
179
- { ...props },
180
- children.props.children ? children.props.children : specs.slots?.default ? slotContent : null,
181
- )
182
-
183
- const handleChange = (key: string, value: any, displayAsFalse: boolean = false) => {
184
- setIteratedKey(iteratedKey + 1)
185
- if (!displayAsFalse && (!value || value == 'false')) {
186
- const { [key]: _, ...rest } = props
187
- setProps(rest)
188
- } else {
189
- setProps((prevProps) => ({
190
- ...prevProps,
191
- [key]: value,
192
- }))
193
- }
194
- }
195
-
196
- return (
197
- <div className="pkt-preview">
198
- <div className="pkt-preview__component-container" data-mode={mode}>
199
- {specs['dark-mode'] && (
200
- <div className="pkt-preview__mode">
201
- <PktCheckbox
202
- id="mode"
203
- label="Dark mode"
204
- type="checkbox"
205
- checked={mode === 'dark'}
206
- onChange={(e) => setMode(e.target.checked ? 'dark' : 'light')}
207
- labelPosition="right"
208
- isSwitch
209
- />
210
- </div>
211
- )}
212
- <div
213
- className={`pkt-preview__component ${fullWidth && 'pkt-preview__component--fullwidth'}`}
214
- ref={previewComponent}
215
- key={iteratedKey}
216
- >
217
- <div>{component}</div>
218
- </div>
219
- </div>
220
- <PktTabs tabs={tabs} onTabSelected={changeContent}></PktTabs>
221
- <div className={`pkt-grid pkt-preview__opts ${tabs[0].active ? '' : 'pkt-hide'}`}>
222
- {specs.slots?.default && !(children as React.ReactElement).props.children && (
223
- <div className="pkt-cell pkt-cell--span12 pkt-cell--span6-phablet-up">
224
- <PktTag size="small" skin="blue-light" textStyle="thin-text">
225
- children
226
- </PktTag>
227
- <PktTextinput
228
- id="slot"
229
- label="Slot"
230
- helptext={specs.slots.default.description || 'Innholdet i slot'}
231
- type="text"
232
- value={slotContent}
233
- onChange={(e) => setSlotContent(e.currentTarget.value)}
234
- />
235
- </div>
236
- )}
237
- {specs.props && typeof specs.props === 'object' && !Array.isArray(specs.props) ? (
238
- Object.entries(specs.props).map(([key, prop]) => (
239
- <div className="pkt-cell pkt-cell--span12 pkt-cell--span6-phablet-up" key={key}>
240
- <PktTag size="small" skin="blue-light" textStyle="thin-text">
241
- {key}
242
- </PktTag>
243
- <PktPreviewPropEditor
244
- propKey={key}
245
- props={props}
246
- spec={prop}
247
- handleChange={handleChange}
248
- ></PktPreviewPropEditor>
249
- </div>
250
- ))
251
- ) : (
252
- <p>OBS! Specs mangler props!</p>
253
- )}
254
- </div>
255
- <div className={`pkt-preview__code ${tabs[1].active ? '' : 'pkt-hide'}`}>
256
- <SyntaxHighlighter language="jsx" style={prism}>
257
- {jsxContent}
258
- </SyntaxHighlighter>
259
- {CopyButton('jsx', jsxContent)}
260
- </div>
261
- <div className={`pkt-preview__code ${tabs[2].active ? '' : 'pkt-hide'}`}>
262
- <SyntaxHighlighter language="html" style={prism}>
263
- {htmlContent}
264
- </SyntaxHighlighter>
265
- {CopyButton('html', htmlContent)}
266
- </div>
267
- <div className={`pkt-preview__specs ${tabs[3].active ? '' : 'pkt-hide'}`}>
268
- <PktPreviewSpecs specs={specs}></PktPreviewSpecs>
269
- </div>
270
- </div>
271
- )
272
- }
273
-
274
- PktPreview.displayName = 'PktPreview'
@@ -1,118 +0,0 @@
1
- import React, { useState, useEffect } from 'react'
2
- import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
3
- import { a11yDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
4
- import * as prettier from 'prettier/standalone'
5
- import * as parserHtml from 'prettier/parser-html'
6
- import type { TPreviewTreeNode, TSpecs } from './preview-types'
7
- import { PktButton } from '../button/Button'
8
- import { PktTabs } from '../tabs/Tabs'
9
-
10
- import { sanitizeCode, buildElementsCode, buildReactCode } from './preview-utils'
11
-
12
- export const PktPreviewCode: React.FC<{
13
- tree: TPreviewTreeNode
14
- specs: TSpecs
15
- html: string
16
- }> = ({ tree, specs, html }) => {
17
- const [tabs, setTabs] = useState([
18
- { text: 'Punkt React', icon: 'react', active: true },
19
- { text: specs[tree.spec || ''].isElement ? 'Punkt Elements' : 'HTML', icon: 'code', active: false },
20
- ])
21
- const [copied, setCopied] = useState<'jsx' | 'html' | ''>('')
22
- const [activeTab, setActiveTab] = useState(0)
23
-
24
- const htmlCodeRaw = specs[tree.spec || ''].isElement
25
- ? (buildElementsCode(tree, specs) ?? '').replace(/^\s*\n|\n\s*$/g, '').trim()
26
- : html.replace(/<\!--.*?-->/g, '')
27
- const [htmlCode, setHtmlCode] = useState('') // Start as empty string
28
- useEffect(() => {
29
- if (htmlCodeRaw) {
30
- prettier
31
- .format(htmlCodeRaw, {
32
- parser: 'html',
33
- plugins: [parserHtml as any],
34
- singleAttributePerLine: true,
35
- bracketSameLine: true,
36
- })
37
- .then((formatted) => setHtmlCode(formatted.trim()))
38
- .catch(() => setHtmlCode(htmlCodeRaw)) // fallback if prettier fails
39
- } else {
40
- setHtmlCode('')
41
- }
42
- }, [htmlCodeRaw])
43
-
44
- const jsxCodeRaw = (buildReactCode(tree, specs) ?? '').replace(/^\s*\n|\n\s*$/g, '').trim()
45
- const [jsxCode, setJsxCode] = useState('') // Start as empty string
46
- useEffect(() => {
47
- if (jsxCodeRaw) {
48
- prettier
49
- .format(jsxCodeRaw, {
50
- parser: 'html',
51
- plugins: [parserHtml as any],
52
- singleAttributePerLine: true,
53
- bracketSameLine: true,
54
- })
55
- .then((formatted) => setJsxCode(formatted.trim()))
56
- .catch(() => setJsxCode(jsxCodeRaw)) // fallback if prettier fails
57
- } else {
58
- setJsxCode('')
59
- }
60
- }, [jsxCodeRaw])
61
-
62
- const changeTab = (id: number) => {
63
- setActiveTab(id)
64
- setCopied('')
65
- setTabs((prevTabs) =>
66
- prevTabs.map((tab, index) => ({
67
- ...tab,
68
- active: index === id,
69
- })),
70
- )
71
- }
72
-
73
- const copyToClipboard = (type: 'jsx' | 'html', content: string) => {
74
- navigator.clipboard.writeText(content).then(() => setCopied(type))
75
- }
76
-
77
- return (
78
- <div className="pkt-preview__code-container" data-mode="dark">
79
- <PktTabs tabs={tabs} onTabSelected={changeTab} />
80
- <div className={`pkt-preview__code${activeTab === 0 ? '' : ' pkt-hide'}`}>
81
- <SyntaxHighlighter language="jsx" style={a11yDark}>
82
- {typeof jsxCode === 'string' && jsxCode.length > 0 ? sanitizeCode(jsxCode) : ''}
83
- </SyntaxHighlighter>
84
-
85
- <div className="pkt-preview__copy">
86
- {copied === 'jsx' && <span className="pkt-preview__copied">Kode kopiert til utklippstavle</span>}
87
- <PktButton
88
- skin="tertiary"
89
- variant="icon-only"
90
- size="small"
91
- iconName="copy"
92
- onClick={() => copyToClipboard('jsx', jsxCode)}
93
- >
94
- Kopier kode
95
- </PktButton>
96
- </div>
97
- </div>
98
- <div className={`pkt-preview__code${activeTab === 1 ? '' : ' pkt-hide'}`}>
99
- <SyntaxHighlighter language="markup" style={a11yDark}>
100
- {typeof htmlCode === 'string' && htmlCode.length > 0 ? sanitizeCode(htmlCode) : ''}
101
- </SyntaxHighlighter>
102
-
103
- <div className="pkt-preview__copy">
104
- {copied === 'html' && <span className="pkt-preview__copied">Kode kopiert til utklippstavle</span>}
105
- <PktButton
106
- skin="tertiary"
107
- variant="icon-only"
108
- size="small"
109
- iconName="copy"
110
- onClick={() => copyToClipboard('html', htmlCode.trim())}
111
- >
112
- Kopier kode
113
- </PktButton>
114
- </div>
115
- </div>
116
- </div>
117
- )
118
- }
@@ -1,266 +0,0 @@
1
- 'use client'
2
-
3
- import { FC } from 'react'
4
- import { PktCheckbox } from '../checkbox/Checkbox'
5
- import { PktDatepicker } from '../datepicker/Datepicker'
6
- import icons from '@oslokommune/punkt-assets/dist/icons/icons.json'
7
- import { PktButton } from '../button/Button'
8
- import { PktAccordion } from '../accordion/Accordion'
9
- import { PktAccordionItem } from '../accordion/AccordionItem'
10
- import { PktTextinput } from '../textinput/Textinput'
11
- import { IPropEditorProps } from './preview-types'
12
- import { PktTag } from '../tag/Tag'
13
- import { PktSelect } from '../select/Select'
14
- import { PktTextarea } from '../textarea/Textarea'
15
-
16
- export const PktPreviewPropEditor: FC<IPropEditorProps> = ({ propKey, spec, props, hideKey, handleChange }) => {
17
- const editorTypes = {
18
- BOOLEAN: 'boolean',
19
- ICON: 'icon',
20
- ARRAY: 'array',
21
- OBJECT: 'object',
22
- DATE: 'ISOdatestring',
23
- NUMBER: 'number',
24
- LONGSTRING: 'longstring',
25
- STRING: 'string',
26
- }
27
-
28
- // Boolean
29
- if (spec.type === editorTypes.BOOLEAN) {
30
- // Support both boolean and { value, displayAsFalse }
31
- const raw = props[propKey]
32
- const checked = typeof raw === 'object' && raw !== null && 'value' in raw ? raw.value : !!raw
33
- return (
34
- <PktCheckbox
35
- id={propKey}
36
- label={spec.name || propKey}
37
- tagText={hideKey ? undefined : propKey}
38
- requiredTag={spec.required}
39
- type="checkbox"
40
- checked={checked}
41
- onChange={(e) => {
42
- handleChange(
43
- propKey,
44
- spec.displayAsFalse ? { value: e.target.checked, displayAsFalse: true } : e.target.checked,
45
- )
46
- }}
47
- labelPosition="right"
48
- isSwitch
49
- />
50
- )
51
- }
52
-
53
- // Icon (dropdown)
54
- if (spec.type === editorTypes.ICON) {
55
- const value = props[propKey] || ''
56
- return (
57
- <PktSelect
58
- label={spec.name || propKey}
59
- id={propKey}
60
- tagText={hideKey ? undefined : propKey}
61
- value={value}
62
- onChange={(e) => {
63
- handleChange(propKey, e.target.value)
64
- }}
65
- requiredTag={spec.required}
66
- >
67
- <option value=""></option>
68
- {(icons as { id: string }[]).map((icon) => (
69
- <option key={icon.id} value={icon.id}>
70
- {icon.id}
71
- </option>
72
- ))}
73
- </PktSelect>
74
- )
75
- }
76
-
77
- // Date
78
- if (spec.type === editorTypes.DATE) {
79
- const value = props[propKey] || ''
80
- return (
81
- <div>
82
- <PktDatepicker
83
- id={propKey}
84
- label={spec.name || propKey}
85
- value={value}
86
- multiple={!!(spec.variant === 'multiple')}
87
- maxlength={999}
88
- tagText={propKey}
89
- onChange={(e) => {
90
- handleChange(propKey, (e.target as HTMLInputElement).value)
91
- }}
92
- requiredTag={spec.required}
93
- />
94
- </div>
95
- )
96
- }
97
-
98
- // Enum (array of values)
99
- if (Array.isArray(spec) || Array.isArray(spec.type) || Array.isArray(spec.values)) {
100
- const arr = Array.isArray(spec) ? [...spec] : spec.values ? [...spec.values] : [...spec.type]
101
- const value = props[propKey] || ''
102
- return (
103
- <PktSelect
104
- label={spec.name || propKey}
105
- id={propKey}
106
- value={value}
107
- onChange={(e) => handleChange(propKey, e.currentTarget.value)}
108
- required={spec.required}
109
- requiredTag={spec.required}
110
- tagText={hideKey ? undefined : propKey}
111
- >
112
- <option value=""></option>
113
- {arr.map((option: string) => (
114
- <option key={option} value={option}>
115
- {option}
116
- </option>
117
- ))}
118
- </PktSelect>
119
- )
120
- }
121
-
122
- // Number
123
- if (spec.type === editorTypes.NUMBER) {
124
- const value = props[propKey] ?? ''
125
- return (
126
- <PktTextinput
127
- label={spec.name || propKey}
128
- id={propKey}
129
- requiredTag={spec.required}
130
- required={spec.required}
131
- tagText={hideKey ? undefined : propKey}
132
- type="number"
133
- value={value}
134
- onChange={(e) => {
135
- handleChange(propKey, parseFloat(e.currentTarget.value))
136
- }}
137
- />
138
- )
139
- }
140
-
141
- // Object
142
- if (spec.type === editorTypes.OBJECT) {
143
- const value = props[propKey] ? { ...props[propKey] } : {}
144
- return (
145
- <div>
146
- <h2 className="pkt-inputwrapper__label">
147
- {spec.name || propKey} <PktTag skin="gray">{propKey}</PktTag>
148
- </h2>
149
- <fieldset className="pkt-preview__object-editor">
150
- {Object.entries(spec.properties).map(([key, childSpec]) => (
151
- <PktPreviewPropEditor
152
- key={key}
153
- propKey={key}
154
- props={value}
155
- spec={childSpec}
156
- hideKey={hideKey}
157
- handleChange={(childKey, childValue) => {
158
- handleChange(propKey, { ...value, [childKey]: childValue })
159
- }}
160
- />
161
- ))}
162
- </fieldset>
163
- </div>
164
- )
165
- }
166
-
167
- // Array of objects
168
- if (spec.type === editorTypes.ARRAY && spec.items.type === editorTypes.OBJECT) {
169
- const value = Array.isArray(props[propKey]) ? [...props[propKey]] : []
170
- const addObjectToArray = () => {
171
- handleChange(propKey, [...value, {}])
172
- }
173
- const removeObjectFromArray = (index: number) => {
174
- handleChange(
175
- propKey,
176
- value.filter((_, i) => i !== index),
177
- )
178
- }
179
- const handleArrayChange = (index: number, childKey: string, childValue: any) => {
180
- const newArr = value.map((item, i) => (i === index ? { ...item, [childKey]: childValue } : item))
181
- handleChange(propKey, newArr)
182
- }
183
- return (
184
- <div>
185
- <h2 className="pkt-inputwrapper__label">
186
- {spec.name || propKey} {hideKey ? '' : <PktTag skin="gray">{propKey}</PktTag>}
187
- </h2>
188
- <PktAccordion skin="outlined" compact>
189
- {value.map((item: any, index: number) => (
190
- <div key={index}>
191
- <PktAccordionItem
192
- id={`array-item-${index}`}
193
- title={`${spec.items.name || propKey || `Item`} ${index + 1}`}
194
- >
195
- <div className="pkt-preview__array-item">
196
- {Object.entries(spec.items.properties).map(([key, childSpec]) => (
197
- <PktPreviewPropEditor
198
- key={key}
199
- propKey={key}
200
- props={item}
201
- spec={childSpec}
202
- hideKey={hideKey}
203
- handleChange={(childKey, childValue) => handleArrayChange(index, childKey, childValue)}
204
- />
205
- ))}
206
- <div className="pkt-preview__remove-object">
207
- <PktButton
208
- onClick={() => {
209
- confirm('Er du sikker på at du vil fjerne dette elementet?') && removeObjectFromArray(index)
210
- }}
211
- size="small"
212
- skin="tertiary"
213
- variant="icon-only"
214
- iconName="trash-can"
215
- >
216
- Fjern denne
217
- </PktButton>
218
- </div>
219
- </div>
220
- </PktAccordionItem>
221
- </div>
222
- ))}
223
- </PktAccordion>
224
- <div className="pkt-preview__add-object">
225
- <PktButton onClick={addObjectToArray} size="small" skin="tertiary" variant="icon-left" iconName="plus-circle">
226
- Legg til {spec.items.name || propKey || 'element'}
227
- </PktButton>
228
- </div>
229
- </div>
230
- )
231
- }
232
-
233
- if (spec.type === editorTypes.LONGSTRING) {
234
- // Long string input
235
- const value = props[propKey] || ''
236
- return (
237
- <PktTextarea
238
- tagText={hideKey ? undefined : propKey}
239
- id={propKey}
240
- label={spec.name || propKey}
241
- value={value}
242
- onChange={(e) => {
243
- handleChange(propKey, e.currentTarget.value)
244
- }}
245
- required={spec.required}
246
- requiredTag={spec.required}
247
- />
248
- )
249
- }
250
-
251
- // Default: string
252
- const value = props[propKey] ?? ''
253
- return (
254
- <PktTextinput
255
- label={spec.name || propKey}
256
- tagText={hideKey ? undefined : propKey}
257
- id={propKey}
258
- required={spec.required}
259
- requiredTag={spec.required}
260
- value={value}
261
- onChange={(e) => {
262
- handleChange(propKey, e.currentTarget.value)
263
- }}
264
- />
265
- )
266
- }