@oslokommune/punkt-react 13.2.4 → 13.3.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 (46) hide show
  1. package/CHANGELOG.md +1329 -1038
  2. package/README.md +1 -1
  3. package/dist/index.d.ts +32 -14
  4. package/dist/punkt-react.es.js +43126 -23132
  5. package/dist/punkt-react.umd.js +527 -507
  6. package/package.json +5 -7
  7. package/src/components/index.ts +5 -3
  8. package/src/components/linkcard/LinkCard.tsx +1 -1
  9. package/src/components/preview/Preview.tsx +4 -2
  10. package/src/components/preview/PreviewCode.tsx +118 -0
  11. package/src/components/preview/PreviewPropEditor.tsx +165 -113
  12. package/src/components/preview/PreviewSpecs.tsx +2 -24
  13. package/src/components/preview/PreviewWithJson.tsx +519 -0
  14. package/src/components/preview/preview-types.ts +42 -0
  15. package/src/components/preview/preview-utils.ts +226 -0
  16. package/src/components/preview/previewJson/accordion.json +84 -0
  17. package/src/components/preview/previewJson/alert.json +27 -0
  18. package/src/components/preview/previewJson/backlink.json +14 -0
  19. package/src/components/preview/previewJson/breadcrumbs.json +17 -0
  20. package/src/components/preview/previewJson/button.json +28 -0
  21. package/src/components/preview/previewJson/card.json +41 -0
  22. package/src/components/preview/previewJson/checkbox.json +21 -0
  23. package/src/components/preview/previewJson/combobox.json +24 -0
  24. package/src/components/preview/previewJson/consent.json +14 -0
  25. package/src/components/preview/previewJson/datepicker.json +14 -0
  26. package/src/components/preview/previewJson/footer-simple.json +17 -0
  27. package/src/components/preview/previewJson/footer.json +29 -0
  28. package/src/components/preview/previewJson/header.json +34 -0
  29. package/src/components/preview/previewJson/icon.json +13 -0
  30. package/src/components/preview/previewJson/input-wrapper.json +39 -0
  31. package/src/components/preview/previewJson/link.json +28 -0
  32. package/src/components/preview/previewJson/linkcard.json +30 -0
  33. package/src/components/preview/previewJson/loader.json +28 -0
  34. package/src/components/preview/previewJson/messagebox.json +28 -0
  35. package/src/components/preview/previewJson/modal.json +65 -0
  36. package/src/components/preview/previewJson/progressbar.json +16 -0
  37. package/src/components/preview/previewJson/radiobutton.json +21 -0
  38. package/src/components/preview/previewJson/searchinput.json +20 -0
  39. package/src/components/preview/previewJson/select.json +73 -0
  40. package/src/components/preview/previewJson/steps.json +72 -0
  41. package/src/components/preview/previewJson/table.json +130 -0
  42. package/src/components/preview/previewJson/tabs.json +33 -0
  43. package/src/components/preview/previewJson/tag.json +26 -0
  44. package/src/components/preview/previewJson/textarea.json +18 -0
  45. package/src/components/preview/previewJson/textinput.json +18 -0
  46. package/src/components/searchinput/SearchInput.tsx +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oslokommune/punkt-react",
3
- "version": "13.2.4",
3
+ "version": "13.3.0",
4
4
  "description": "React komponentbibliotek til Punkt, et designsystem laget av Oslo Origo",
5
5
  "homepage": "https://punkt.oslo.kommune.no",
6
6
  "author": "Team Designsystem, Oslo Origo",
@@ -38,18 +38,16 @@
38
38
  "dependencies": {
39
39
  "@lit-labs/ssr-dom-shim": "^1.2.1",
40
40
  "@lit/react": "^1.0.7",
41
- "@oslokommune/punkt-elements": "^13.2.4",
42
- "angular-html-parser": "^6.0.2",
43
- "html-format": "^1.1.7",
41
+ "@oslokommune/punkt-elements": "^13.3.0",
44
42
  "prettier": "^3.3.3",
45
43
  "react-element-to-jsx-string": "^15.0.0",
46
44
  "react-hook-form": "^7.53.0",
47
- "react-syntax-highlighter": "^15.5.0"
45
+ "react-syntax-highlighter": "^15.6.1"
48
46
  },
49
47
  "devDependencies": {
50
48
  "@babel/plugin-proposal-private-property-in-object": "^7.18.6",
51
49
  "@oslokommune/punkt-assets": "^13.0.0",
52
- "@oslokommune/punkt-css": "^13.2.3",
50
+ "@oslokommune/punkt-css": "^13.3.0",
53
51
  "@testing-library/jest-dom": "^6.5.0",
54
52
  "@testing-library/react": "^16.0.1",
55
53
  "@testing-library/user-event": "^14.5.2",
@@ -112,5 +110,5 @@
112
110
  "url": "https://github.com/oslokommune/punkt/issues"
113
111
  },
114
112
  "license": "MIT",
115
- "gitHead": "f777f10f0637de10ff45fe6a5b6d9c7e4abd70ff"
113
+ "gitHead": "82281bdd30fc0a0a1e47b3a52199e59aed83b65d"
116
114
  }
@@ -12,6 +12,7 @@ export { PktDatepicker } from './datepicker/Datepicker'
12
12
  export { PktFooter } from './footer/Footer'
13
13
  export { PktFooterSimple } from './footerSimple/FooterSimple'
14
14
  export { PktHeader } from './header/Header'
15
+ export { PktHeading } from './heading/Heading'
15
16
  export { PktHelptext } from './helptext/Helptext'
16
17
  export { PktIcon } from './icon/Icon'
17
18
  export { PktInput } from './input/Input'
@@ -22,18 +23,19 @@ export { PktLoader } from './loader/Loader'
22
23
  export { PktMessagebox } from './messagebox/Messagebox'
23
24
  export { PktModal } from './modal/Modal'
24
25
  export { PktPreview } from './preview/Preview'
26
+ export { PktPreviewWithJson } from './preview/PreviewWithJson'
25
27
  export { PktProgressbar } from './progressbar/Progressbar'
26
28
  export { PktRadioButton } from './radio/RadioButton'
27
29
  export { PktSearchInput } from './searchinput/SearchInput'
28
30
  export { PktSelect } from './select/Select'
29
- export { PktStepper } from './stepper/Stepper'
30
31
  export { PktStep } from './stepper/Step'
32
+ export { PktStepper } from './stepper/Stepper'
31
33
  export { PktTable } from './table/Table'
34
+ export { PktTableBody } from './table/TableBody'
32
35
  export { PktTableDataCell } from './table/TableDataCell'
33
- export { PktTableHeaderCell } from './table/TableHeaderCell'
34
36
  export { PktTableHeader } from './table/TableHeader'
37
+ export { PktTableHeaderCell } from './table/TableHeaderCell'
35
38
  export { PktTableRow } from './table/TableRow'
36
- export { PktTableBody } from './table/TableBody'
37
39
  export { PktTabs } from './tabs/Tabs'
38
40
  export { PktTag } from './tag/Tag'
39
41
  export { PktTextarea } from './textarea/Textarea'
@@ -6,7 +6,7 @@ import { PktLinkCard as PktElLinkCard } from '@oslokommune/punkt-elements'
6
6
  import type { PktElType, PktElConstructor } from '@/interfaces/IPktElements'
7
7
 
8
8
  export interface IPktLinkCard extends PktElType {
9
- skin?: 'normal' | 'blue' | 'beige' | 'green' | 'gray' | 'beige-outline' | 'gray-outline'
9
+ skin?: 'normal' | 'no-padding' | 'blue' | 'beige' | 'green' | 'gray' | 'beige-outline' | 'gray-outline'
10
10
  title?: string
11
11
  href?: string
12
12
  iconName?: string
@@ -12,7 +12,8 @@ import { PktCheckbox } from '../checkbox/Checkbox'
12
12
  import { PktTabs } from '../tabs/Tabs'
13
13
  import { PktTag } from '../tag/Tag'
14
14
  import { PktTextinput } from '../textinput/Textinput'
15
- import { PktPreviewSpecs, ISpecObject } from './PreviewSpecs'
15
+ import { PktPreviewSpecs } from './PreviewSpecs'
16
+ import type { ISpecObject } from './preview-types'
16
17
  import { PktPreviewPropEditor } from './PreviewPropEditor'
17
18
 
18
19
  SyntaxHighlighter.registerLanguage('jsx', jsx)
@@ -63,7 +64,8 @@ export const PktPreview: React.FC<PreviewProps> = ({ specs, children, fullWidth
63
64
  const [htmlContent, setHtmlContent] = useState('')
64
65
  const [jsxContent, setJsxContent] = useState('')
65
66
  const [copied, setCopied] = useState('')
66
- const [slotContent, setSlotContent] = useState('Rediger innhold')
67
+ const [slotContent, setSlotContent] = useState('Innhold')
68
+
67
69
  const [tabs, setTabs] = useState([
68
70
  {
69
71
  text: 'Rediger',
@@ -0,0 +1,118 @@
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
+ }
@@ -3,20 +3,17 @@
3
3
  import { FC } from 'react'
4
4
  import { PktCheckbox } from '../checkbox/Checkbox'
5
5
  import { PktDatepicker } from '../datepicker/Datepicker'
6
- import { PktInputWrapper } from '../inputwrapper/InputWrapper'
7
- import { PktSelect } from '../select/Select'
8
- import { PktTextinput } from '../textinput/Textinput'
9
6
  import icons from '@oslokommune/punkt-assets/dist/icons/icons.json'
10
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'
11
15
 
12
- interface PropEditorProps {
13
- propKey: string
14
- spec: any
15
- props: any
16
- handleChange: (key: string, value: any, displayAsFalse?: boolean) => void
17
- }
18
-
19
- export const PktPreviewPropEditor: FC<PropEditorProps> = ({ propKey, spec, props, handleChange }) => {
16
+ export const PktPreviewPropEditor: FC<IPropEditorProps> = ({ propKey, spec, props, hideKey, handleChange }) => {
20
17
  const editorTypes = {
21
18
  BOOLEAN: 'boolean',
22
19
  ICON: 'icon',
@@ -24,59 +21,47 @@ export const PktPreviewPropEditor: FC<PropEditorProps> = ({ propKey, spec, props
24
21
  OBJECT: 'object',
25
22
  DATE: 'ISOdatestring',
26
23
  NUMBER: 'number',
24
+ LONGSTRING: 'longstring',
27
25
  STRING: 'string',
28
26
  }
29
27
 
30
- const handleObjectChange = (key: string, value: any) => {
31
- handleChange(propKey, { ...props[propKey], [key]: value })
32
- }
33
-
34
- const handleArrayChange = (index: number, key: string, value: any) => {
35
- const newArray = [...props[propKey]]
36
- newArray[index] = { ...newArray[index], [key]: value }
37
- handleChange(propKey, newArray)
38
- }
39
-
40
- const addObjectToArray = () => {
41
- const newArray = [...props[propKey], {}]
42
- handleChange(propKey, newArray)
43
- }
44
-
45
- const removeObjectFromArray = (index: number) => {
46
- const newArray = props[propKey].filter((_: any, i: number) => i !== index)
47
- handleChange(propKey, newArray)
48
- }
49
-
28
+ // Boolean
50
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
51
33
  return (
52
- <PktInputWrapper
34
+ <PktCheckbox
35
+ id={propKey}
53
36
  label={spec.name || propKey}
54
- forId={propKey}
55
- helptext={spec.description || null}
56
- hasFieldset
37
+ tagText={hideKey ? undefined : propKey}
57
38
  requiredTag={spec.required}
58
- >
59
- <PktCheckbox
60
- id={propKey}
61
- label={propKey}
62
- type="checkbox"
63
- checked={props[propKey] || false}
64
- onChange={(e) => handleChange(propKey, e.target.checked, spec.displayAsFalse)}
65
- labelPosition="right"
66
- isSwitch
67
- />
68
- </PktInputWrapper>
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
+ />
69
50
  )
70
51
  }
71
52
 
53
+ // Icon (dropdown)
72
54
  if (spec.type === editorTypes.ICON) {
55
+ const value = props[propKey] || ''
73
56
  return (
74
57
  <PktSelect
75
58
  label={spec.name || propKey}
76
- helptext={spec.description || null}
77
59
  id={propKey}
78
- value={props[propKey] || ''}
79
- onChange={(e) => handleChange(propKey, e.target.value)}
60
+ tagText={hideKey ? undefined : propKey}
61
+ value={value}
62
+ onChange={(e) => {
63
+ handleChange(propKey, e.target.value)
64
+ }}
80
65
  requiredTag={spec.required}
81
66
  >
82
67
  <option value=""></option>
@@ -89,31 +74,40 @@ export const PktPreviewPropEditor: FC<PropEditorProps> = ({ propKey, spec, props
89
74
  )
90
75
  }
91
76
 
77
+ // Date
92
78
  if (spec.type === editorTypes.DATE) {
79
+ const value = props[propKey] || ''
93
80
  return (
94
- <PktDatepicker
95
- id={propKey}
96
- label={spec.name || propKey}
97
- helptext={spec.description || null}
98
- value={props[propKey] || ''}
99
- multiple={!!(spec.variant === 'multiple')}
100
- maxlength={999}
101
- onChange={(e) => handleChange(propKey, (e.target as HTMLInputElement).value)}
102
- requiredTag={spec.required}
103
- />
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>
104
95
  )
105
96
  }
106
97
 
98
+ // Enum (array of values)
107
99
  if (Array.isArray(spec) || Array.isArray(spec.type) || Array.isArray(spec.values)) {
108
- const arr = Array.isArray(spec) ? spec : spec.values || spec.type
100
+ const arr = Array.isArray(spec) ? [...spec] : spec.values ? [...spec.values] : [...spec.type]
101
+ const value = props[propKey] || ''
109
102
  return (
110
103
  <PktSelect
111
104
  label={spec.name || propKey}
112
- helptext={spec.description || null}
113
105
  id={propKey}
114
- value={props[propKey]}
115
- onChange={(e) => handleChange(propKey, e.target.value)}
106
+ value={value}
107
+ onChange={(e) => handleChange(propKey, e.currentTarget.value)}
108
+ required={spec.required}
116
109
  requiredTag={spec.required}
110
+ tagText={hideKey ? undefined : propKey}
117
111
  >
118
112
  <option value=""></option>
119
113
  {arr.map((option: string) => (
@@ -125,90 +119,148 @@ export const PktPreviewPropEditor: FC<PropEditorProps> = ({ propKey, spec, props
125
119
  )
126
120
  }
127
121
 
122
+ // Number
128
123
  if (spec.type === editorTypes.NUMBER) {
124
+ const value = props[propKey] ?? ''
129
125
  return (
130
126
  <PktTextinput
131
- id={propKey}
132
127
  label={spec.name || propKey}
133
- helptext={spec.description || null}
134
- type="number"
135
- value={props[propKey] || ''}
136
- onChange={(e) => handleChange(propKey, parseFloat(e.currentTarget.value))}
128
+ id={propKey}
137
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
+ }}
138
137
  />
139
138
  )
140
139
  }
141
140
 
141
+ // Object
142
142
  if (spec.type === editorTypes.OBJECT) {
143
- if (!props[propKey]) {
144
- props[propKey] = {}
145
- }
143
+ const value = props[propKey] ? { ...props[propKey] } : {}
146
144
  return (
147
145
  <div>
148
- <div className="pkt-inputwrapper__label">
149
- <h2>{spec.name || propKey}</h2>
150
- </div>
151
- {spec.description && <p>{spec.description}</p>}
152
-
153
- <fieldset>
154
- <legend>{spec.name || propKey}</legend>
155
- {Object.entries(spec.properties).map(([key, prop]) => (
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]) => (
156
151
  <PktPreviewPropEditor
157
152
  key={key}
158
153
  propKey={key}
159
- props={props[propKey]}
160
- spec={prop}
161
- handleChange={(key, value) => handleObjectChange(key, value)}
162
- ></PktPreviewPropEditor>
154
+ props={value}
155
+ spec={childSpec}
156
+ hideKey={hideKey}
157
+ handleChange={(childKey, childValue) => {
158
+ handleChange(propKey, { ...value, [childKey]: childValue })
159
+ }}
160
+ />
163
161
  ))}
164
162
  </fieldset>
165
163
  </div>
166
164
  )
167
165
  }
168
166
 
167
+ // Array of objects
169
168
  if (spec.type === editorTypes.ARRAY && spec.items.type === editorTypes.OBJECT) {
170
- if (!props[propKey]) {
171
- props[propKey] = []
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)
172
182
  }
173
183
  return (
174
184
  <div>
175
- <div className="pkt-inputwrapper__label">
176
- <h2>{spec.name || propKey}</h2>
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>
177
228
  </div>
178
- {spec.description && <p>{spec.description}</p>}
179
- {props[propKey].map((item: any, index: number) => (
180
- <div key={index}>
181
- <fieldset>
182
- <legend>Item {index + 1}</legend>
183
- {Object.entries(spec.items.properties).map(([key, prop]) => (
184
- <PktPreviewPropEditor
185
- key={key}
186
- propKey={key}
187
- props={item}
188
- spec={prop}
189
- handleChange={(key, value) => handleArrayChange(index, key, value)}
190
- ></PktPreviewPropEditor>
191
- ))}
192
- <PktButton onClick={() => removeObjectFromArray(index)}>Fjern denne</PktButton>
193
- </fieldset>
194
- </div>
195
- ))}
196
- <PktButton onClick={addObjectToArray}>Legg til ny</PktButton>
197
229
  </div>
198
230
  )
199
231
  }
200
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] ?? ''
201
253
  return (
202
254
  <PktTextinput
203
- id={propKey}
204
255
  label={spec.name || propKey}
205
- helptext={spec.description || null}
206
- type="text"
207
- value={props[propKey] || ''}
208
- onChange={(e) =>
209
- handleChange(propKey, spec.type === 'number' ? parseFloat(e.currentTarget.value) : e.currentTarget.value)
210
- }
256
+ tagText={hideKey ? undefined : propKey}
257
+ id={propKey}
258
+ required={spec.required}
211
259
  requiredTag={spec.required}
260
+ value={value}
261
+ onChange={(e) => {
262
+ handleChange(propKey, e.currentTarget.value)
263
+ }}
212
264
  />
213
265
  )
214
266
  }
@@ -7,32 +7,10 @@ import { PktTableBody } from '../table/TableBody'
7
7
  import { PktTableRow } from '../table/TableRow'
8
8
  import { PktTableHeaderCell } from '../table/TableHeaderCell'
9
9
  import { PktTableDataCell } from '../table/TableDataCell'
10
-
11
- export interface ISpecObject {
12
- name?: string
13
- 'css-class'?: string
14
- 'dark-mode'?: boolean
15
- isElement?: boolean
16
- props?: Record<string, PropObject | string[]>
17
- events?: Record<string, PropObject>
18
- slots?: Record<string, any>
19
- default?: any
20
- }
21
-
22
- type PropObject = {
23
- name?: string
24
- description?: string
25
- type?: string | string[]
26
- variant?: string
27
- converter?: string
28
- reflect?: boolean
29
- default?: any
30
- previewDefault?: any
31
- items?: PropObject
32
- }
10
+ import { ISpecObject } from './preview-types'
33
11
 
34
12
  export const PktPreviewSpecs: React.FC<{ specs: ISpecObject }> = ({ specs }) => {
35
- const formatType = (type: string | string[]) => {
13
+ const formatType = (type: string | string[] | number | number[]) => {
36
14
  if (Array.isArray(type)) {
37
15
  return type.join(' \n')
38
16
  }