@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.
- package/CHANGELOG.md +41 -0
- package/dist/index.d.ts +58 -76
- package/dist/punkt-react.es.js +5331 -35936
- package/dist/punkt-react.umd.js +422 -552
- package/package.json +11 -15
- package/src/components/accordion/Accordion.test.tsx +1 -1
- package/src/components/accordion/Accordion.tsx +1 -1
- package/src/components/accordion/AccordionItem.tsx +6 -5
- package/src/components/alert/Alert.tsx +2 -1
- package/src/components/breadcrumbs/Breadcrumbs.test.tsx +16 -4
- package/src/components/breadcrumbs/Breadcrumbs.tsx +3 -2
- package/src/components/button/Button.tsx +3 -2
- package/src/components/checkbox/Checkbox.tsx +4 -3
- package/src/components/footer/Footer.tsx +6 -5
- package/src/components/footerSimple/FooterSimple.tsx +4 -3
- package/src/components/icon/Icon.test.tsx +6 -19
- package/src/components/index.ts +1 -2
- package/src/components/input/Input.tsx +4 -3
- package/src/components/radio/RadioButton.tsx +3 -2
- package/src/components/searchinput/SearchInput.tsx +3 -3
- package/src/components/stepper/Stepper.tsx +6 -6
- package/src/components/table/Table.tsx +2 -1
- package/src/components/table/TableBody.tsx +2 -1
- package/src/components/table/TableData.tsx +2 -1
- package/src/components/table/TableDataCell.tsx +2 -1
- package/src/components/table/TableHeader.tsx +2 -1
- package/src/components/table/TableHeaderCell.tsx +2 -1
- package/src/components/table/TableRow.tsx +2 -1
- package/src/components/tabs/TabItem.tsx +77 -0
- package/src/components/tabs/Tabs.test.tsx +393 -1
- package/src/components/tabs/Tabs.tsx +86 -65
- package/src/components/tag/Tag.tsx +2 -1
- package/src/components/preview/Preview.tsx +0 -274
- package/src/components/preview/PreviewCode.tsx +0 -118
- package/src/components/preview/PreviewPropEditor.tsx +0 -266
- package/src/components/preview/PreviewSpecs.tsx +0 -125
- package/src/components/preview/PreviewWithJson.tsx +0 -519
- package/src/components/preview/preview-types.ts +0 -42
- package/src/components/preview/preview-utils.ts +0 -226
- package/src/components/preview/previewJson/accordion.json +0 -84
- package/src/components/preview/previewJson/alert.json +0 -27
- package/src/components/preview/previewJson/backlink.json +0 -14
- package/src/components/preview/previewJson/breadcrumbs.json +0 -17
- package/src/components/preview/previewJson/button.json +0 -28
- package/src/components/preview/previewJson/card.json +0 -41
- package/src/components/preview/previewJson/checkbox.json +0 -21
- package/src/components/preview/previewJson/combobox.json +0 -24
- package/src/components/preview/previewJson/consent.json +0 -14
- package/src/components/preview/previewJson/datepicker.json +0 -14
- package/src/components/preview/previewJson/footer-simple.json +0 -17
- package/src/components/preview/previewJson/footer.json +0 -29
- package/src/components/preview/previewJson/header.json +0 -34
- package/src/components/preview/previewJson/icon.json +0 -13
- package/src/components/preview/previewJson/input-wrapper.json +0 -39
- package/src/components/preview/previewJson/link.json +0 -28
- package/src/components/preview/previewJson/linkcard.json +0 -30
- package/src/components/preview/previewJson/loader.json +0 -28
- package/src/components/preview/previewJson/messagebox.json +0 -28
- package/src/components/preview/previewJson/modal.json +0 -65
- package/src/components/preview/previewJson/progressbar.json +0 -16
- package/src/components/preview/previewJson/radiobutton.json +0 -21
- package/src/components/preview/previewJson/searchinput.json +0 -20
- package/src/components/preview/previewJson/select.json +0 -73
- package/src/components/preview/previewJson/steps.json +0 -72
- package/src/components/preview/previewJson/table.json +0 -130
- package/src/components/preview/previewJson/tabs.json +0 -33
- package/src/components/preview/previewJson/tag.json +0 -26
- package/src/components/preview/previewJson/textarea.json +0 -18
- package/src/components/preview/previewJson/textinput.json +0 -18
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import React from 'react'
|
|
4
|
-
import { PktTable } from '../table/Table'
|
|
5
|
-
import { PktTableHeader } from '../table/TableHeader'
|
|
6
|
-
import { PktTableBody } from '../table/TableBody'
|
|
7
|
-
import { PktTableRow } from '../table/TableRow'
|
|
8
|
-
import { PktTableHeaderCell } from '../table/TableHeaderCell'
|
|
9
|
-
import { PktTableDataCell } from '../table/TableDataCell'
|
|
10
|
-
import { ISpecObject } from './preview-types'
|
|
11
|
-
|
|
12
|
-
export const PktPreviewSpecs: React.FC<{ specs: ISpecObject }> = ({ specs }) => {
|
|
13
|
-
const formatType = (type: string | string[] | number | number[]) => {
|
|
14
|
-
if (Array.isArray(type)) {
|
|
15
|
-
return type.join(' \n')
|
|
16
|
-
}
|
|
17
|
-
return type
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const formatValue = (value: any) => {
|
|
21
|
-
if (typeof value === 'boolean') {
|
|
22
|
-
return value ? 'true' : 'false'
|
|
23
|
-
}
|
|
24
|
-
return value
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<>
|
|
29
|
-
<h2>Egenskaper</h2>
|
|
30
|
-
<PktTable compact>
|
|
31
|
-
<PktTableHeader>
|
|
32
|
-
<PktTableRow>
|
|
33
|
-
<PktTableHeaderCell>Prop</PktTableHeaderCell>
|
|
34
|
-
<PktTableHeaderCell>Navn</PktTableHeaderCell>
|
|
35
|
-
<PktTableHeaderCell>Beskrivelse</PktTableHeaderCell>
|
|
36
|
-
<PktTableHeaderCell>Type</PktTableHeaderCell>
|
|
37
|
-
<PktTableHeaderCell>Standardverdi</PktTableHeaderCell>
|
|
38
|
-
</PktTableRow>
|
|
39
|
-
</PktTableHeader>
|
|
40
|
-
<PktTableBody>
|
|
41
|
-
{Object.entries(specs.props || {}).map(([key, value]) => (
|
|
42
|
-
<PktTableRow key={key}>
|
|
43
|
-
<PktTableDataCell dataLabel="Prop">
|
|
44
|
-
<pre>{key}</pre>
|
|
45
|
-
</PktTableDataCell>
|
|
46
|
-
<PktTableDataCell dataLabel="Navn">{(!Array.isArray(value) && value.name) || key}</PktTableDataCell>
|
|
47
|
-
{Array.isArray(value) ? (
|
|
48
|
-
<>
|
|
49
|
-
<PktTableDataCell dataLabel="Beskrivelse"></PktTableDataCell>
|
|
50
|
-
<PktTableDataCell dataLabel="Type">
|
|
51
|
-
<pre>{value.join('\n')}</pre>
|
|
52
|
-
</PktTableDataCell>
|
|
53
|
-
</>
|
|
54
|
-
) : (
|
|
55
|
-
<>
|
|
56
|
-
<PktTableDataCell dataLabel="Beskrivelse">
|
|
57
|
-
<span dangerouslySetInnerHTML={{ __html: value.description || '' }}></span>
|
|
58
|
-
</PktTableDataCell>
|
|
59
|
-
<PktTableDataCell dataLabel="Type">
|
|
60
|
-
<pre>{value.type && formatType(value.type)}</pre>
|
|
61
|
-
</PktTableDataCell>
|
|
62
|
-
<PktTableDataCell dataLabel="Standardverdi">
|
|
63
|
-
<pre>{formatValue(value.default)}</pre>
|
|
64
|
-
</PktTableDataCell>
|
|
65
|
-
</>
|
|
66
|
-
)}
|
|
67
|
-
</PktTableRow>
|
|
68
|
-
))}
|
|
69
|
-
</PktTableBody>
|
|
70
|
-
</PktTable>
|
|
71
|
-
{specs.events && (
|
|
72
|
-
<>
|
|
73
|
-
<h2>Hendelser / handlinger</h2>
|
|
74
|
-
<PktTable compact>
|
|
75
|
-
<PktTableHeader>
|
|
76
|
-
<PktTableRow>
|
|
77
|
-
<PktTableHeaderCell>Event</PktTableHeaderCell>
|
|
78
|
-
<PktTableHeaderCell>Beskrivelse</PktTableHeaderCell>
|
|
79
|
-
<PktTableHeaderCell>Type</PktTableHeaderCell>
|
|
80
|
-
</PktTableRow>
|
|
81
|
-
</PktTableHeader>
|
|
82
|
-
<PktTableBody>
|
|
83
|
-
{Object.entries(specs.events || {}).map(([key, value]) => (
|
|
84
|
-
<PktTableRow key={key}>
|
|
85
|
-
<PktTableDataCell dataLabel="Event">
|
|
86
|
-
<pre>{key}</pre>
|
|
87
|
-
</PktTableDataCell>
|
|
88
|
-
<PktTableDataCell dataLabel="Beskrivelse">
|
|
89
|
-
<span dangerouslySetInnerHTML={{ __html: value.description || '' }}></span>
|
|
90
|
-
</PktTableDataCell>
|
|
91
|
-
<PktTableDataCell dataLabel="Type">{value.type}</PktTableDataCell>
|
|
92
|
-
</PktTableRow>
|
|
93
|
-
))}
|
|
94
|
-
</PktTableBody>
|
|
95
|
-
</PktTable>
|
|
96
|
-
</>
|
|
97
|
-
)}
|
|
98
|
-
{specs.slots && (
|
|
99
|
-
<>
|
|
100
|
-
<h2>Innhold</h2>
|
|
101
|
-
<PktTable compact>
|
|
102
|
-
<PktTableHeader>
|
|
103
|
-
<PktTableRow>
|
|
104
|
-
<PktTableHeaderCell>Slot</PktTableHeaderCell>
|
|
105
|
-
<PktTableHeaderCell>Beskrivelse</PktTableHeaderCell>
|
|
106
|
-
</PktTableRow>
|
|
107
|
-
</PktTableHeader>
|
|
108
|
-
<PktTableBody>
|
|
109
|
-
{Object.entries(specs.slots || {}).map(([key, value]) => (
|
|
110
|
-
<PktTableRow key={key}>
|
|
111
|
-
<PktTableDataCell dataLabel="Slot">
|
|
112
|
-
<pre>{key}</pre>
|
|
113
|
-
</PktTableDataCell>
|
|
114
|
-
<PktTableDataCell dataLabel="Beskrivelse">
|
|
115
|
-
<span dangerouslySetInnerHTML={{ __html: value.description || '' }}></span>
|
|
116
|
-
</PktTableDataCell>
|
|
117
|
-
</PktTableRow>
|
|
118
|
-
))}
|
|
119
|
-
</PktTableBody>
|
|
120
|
-
</PktTable>
|
|
121
|
-
</>
|
|
122
|
-
)}
|
|
123
|
-
</>
|
|
124
|
-
)
|
|
125
|
-
}
|
|
@@ -1,519 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import React, { useState, useCallback, useRef, useEffect } from 'react'
|
|
4
|
-
|
|
5
|
-
import { PktAccordion } from '../accordion/Accordion'
|
|
6
|
-
import { PktAccordionItem } from '../accordion/AccordionItem'
|
|
7
|
-
import { PktAlert } from '../alert/Alert'
|
|
8
|
-
import { PktBackLink } from '../backlink/BackLink'
|
|
9
|
-
import { PktBreadcrumbs } from '../breadcrumbs/Breadcrumbs'
|
|
10
|
-
import { PktButton } from '../button/Button'
|
|
11
|
-
import { PktCard } from '../card/Card'
|
|
12
|
-
import { PktCheckbox } from '../checkbox/Checkbox'
|
|
13
|
-
import { PktCombobox } from '../combobox/Combobox'
|
|
14
|
-
import { PktConsent } from '../consent/Consent'
|
|
15
|
-
import { PktDatepicker } from '../datepicker/Datepicker'
|
|
16
|
-
import { PktFooter } from '../footer/Footer'
|
|
17
|
-
import { PktFooterSimple } from '../footerSimple/FooterSimple'
|
|
18
|
-
import { PktHeader } from '../header/Header'
|
|
19
|
-
import { PktHeading } from '../heading/Heading'
|
|
20
|
-
import { PktHelptext } from '../helptext/Helptext'
|
|
21
|
-
import { PktIcon } from '../icon/Icon'
|
|
22
|
-
import { PktInputWrapper } from '../inputwrapper/InputWrapper'
|
|
23
|
-
import { PktLink } from '../link/Link'
|
|
24
|
-
import { PktLinkCard } from '../linkcard/LinkCard'
|
|
25
|
-
import { PktLoader } from '../loader/Loader'
|
|
26
|
-
import { PktMessagebox } from '../messagebox/Messagebox'
|
|
27
|
-
import { PktModal } from '../modal/Modal'
|
|
28
|
-
import { PktProgressbar } from '../progressbar/Progressbar'
|
|
29
|
-
import { PktRadioButton } from '../radio/RadioButton'
|
|
30
|
-
import { PktSearchInput } from '../searchinput/SearchInput'
|
|
31
|
-
import { PktSelect } from '../select/Select'
|
|
32
|
-
import { PktStep } from '../stepper/Step'
|
|
33
|
-
import { PktStepper } from '../stepper/Stepper'
|
|
34
|
-
import { PktTable } from '../table/Table'
|
|
35
|
-
import { PktTableBody } from '../table/TableBody'
|
|
36
|
-
import { PktTableDataCell } from '../table/TableDataCell'
|
|
37
|
-
import { PktTableHeader } from '../table/TableHeader'
|
|
38
|
-
import { PktTableHeaderCell } from '../table/TableHeaderCell'
|
|
39
|
-
import { PktTableRow } from '../table/TableRow'
|
|
40
|
-
import { PktTabs } from '../tabs/Tabs'
|
|
41
|
-
import { PktTag } from '../tag/Tag'
|
|
42
|
-
import { PktTextarea } from '../textarea/Textarea'
|
|
43
|
-
import { PktTextinput } from '../textinput/Textinput'
|
|
44
|
-
|
|
45
|
-
import type { ISpecObject, TPropObject } from './preview-types'
|
|
46
|
-
import {
|
|
47
|
-
attachSpecStrings,
|
|
48
|
-
getAllowedChildren,
|
|
49
|
-
getSpecKeyForNode,
|
|
50
|
-
normalizeChildren,
|
|
51
|
-
updateTree,
|
|
52
|
-
} from './preview-utils'
|
|
53
|
-
import { PktPreviewPropEditor } from './PreviewPropEditor'
|
|
54
|
-
import { PktPreviewCode } from './PreviewCode'
|
|
55
|
-
|
|
56
|
-
const componentMap: Record<string, React.ElementType> = {
|
|
57
|
-
PktAccordion,
|
|
58
|
-
PktAccordionItem,
|
|
59
|
-
PktAlert,
|
|
60
|
-
PktBackLink,
|
|
61
|
-
PktBreadcrumbs,
|
|
62
|
-
PktButton,
|
|
63
|
-
PktCard,
|
|
64
|
-
PktCheckbox,
|
|
65
|
-
PktCombobox,
|
|
66
|
-
PktConsent,
|
|
67
|
-
PktDatepicker,
|
|
68
|
-
PktFooter,
|
|
69
|
-
PktFooterSimple,
|
|
70
|
-
PktHeader,
|
|
71
|
-
PktHeading,
|
|
72
|
-
PktHelptext,
|
|
73
|
-
PktIcon,
|
|
74
|
-
PktInputWrapper,
|
|
75
|
-
PktLink,
|
|
76
|
-
PktLinkCard,
|
|
77
|
-
PktLoader,
|
|
78
|
-
PktMessagebox,
|
|
79
|
-
PktModal,
|
|
80
|
-
PktProgressbar,
|
|
81
|
-
PktRadioButton,
|
|
82
|
-
PktSearchInput,
|
|
83
|
-
PktSelect,
|
|
84
|
-
PktStep,
|
|
85
|
-
PktStepper,
|
|
86
|
-
PktTable,
|
|
87
|
-
PktTableBody,
|
|
88
|
-
PktTableDataCell,
|
|
89
|
-
PktTableHeader,
|
|
90
|
-
PktTableHeaderCell,
|
|
91
|
-
PktTableRow,
|
|
92
|
-
PktTabs,
|
|
93
|
-
PktTag,
|
|
94
|
-
PktTextarea,
|
|
95
|
-
PktTextinput,
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
interface PreviewProps {
|
|
99
|
-
specs: Record<string, ISpecObject>
|
|
100
|
-
previewJson: any
|
|
101
|
-
fullWidth?: boolean
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const CATEGORIES = {
|
|
105
|
-
contents: 'Innhold',
|
|
106
|
-
ui: 'Utseende',
|
|
107
|
-
functionality: 'Funksjonalitet',
|
|
108
|
-
accessibility: 'Tilgjengelighet',
|
|
109
|
-
tech: 'Teknisk',
|
|
110
|
-
other: 'Innstillinger',
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function renderFromSpec(node: any, key: string | null = null, renderStringWithSpan: boolean = true): React.ReactNode {
|
|
114
|
-
if (typeof node === 'string') return node
|
|
115
|
-
if (node?.type === 'text') {
|
|
116
|
-
// Render the text node's children as plain text or HTML
|
|
117
|
-
return renderStringWithSpan ? (
|
|
118
|
-
<span dangerouslySetInnerHTML={{ __html: node.children ?? '' }} key={key} />
|
|
119
|
-
) : (
|
|
120
|
-
node.children
|
|
121
|
-
)
|
|
122
|
-
}
|
|
123
|
-
const { type, props = {}, children } = node
|
|
124
|
-
const Comp = componentMap[type] || type
|
|
125
|
-
if (!Comp) {
|
|
126
|
-
return null // Component not found
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
let renderedChildren: React.ReactNode = null
|
|
130
|
-
if (Array.isArray(children)) {
|
|
131
|
-
renderedChildren = children.map((child, i) => renderFromSpec(child, child.props?.id || i, type !== 'option'))
|
|
132
|
-
} else if (children) {
|
|
133
|
-
renderedChildren = renderFromSpec(children, children.props?.id || 0, type !== 'option')
|
|
134
|
-
}
|
|
135
|
-
return (
|
|
136
|
-
<Comp
|
|
137
|
-
key={key}
|
|
138
|
-
{...Object.fromEntries(
|
|
139
|
-
Object.entries(props).map(([k, v]) =>
|
|
140
|
-
typeof v === 'object' && v !== null && 'displayAsFalse' in v && 'value' in v
|
|
141
|
-
? [k, (v as { value: any }).value]
|
|
142
|
-
: [k, v],
|
|
143
|
-
),
|
|
144
|
-
)}
|
|
145
|
-
>
|
|
146
|
-
{typeof renderedChildren === 'string' && renderStringWithSpan ? (
|
|
147
|
-
<span dangerouslySetInnerHTML={{ __html: renderedChildren }} />
|
|
148
|
-
) : (
|
|
149
|
-
renderedChildren
|
|
150
|
-
)}
|
|
151
|
-
</Comp>
|
|
152
|
-
)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Recursive prop editor
|
|
156
|
-
const RecursivePropEditor = React.memo(function RecursivePropEditor({
|
|
157
|
-
node,
|
|
158
|
-
path = [],
|
|
159
|
-
previewSpec,
|
|
160
|
-
specs,
|
|
161
|
-
onChange,
|
|
162
|
-
onAddChild,
|
|
163
|
-
onRemoveChild,
|
|
164
|
-
}: {
|
|
165
|
-
node: any
|
|
166
|
-
previewSpec: any
|
|
167
|
-
path?: (string | number)[]
|
|
168
|
-
specs: Record<string, ISpecObject>
|
|
169
|
-
onChange: (path: (string | number)[], newNode: any) => void
|
|
170
|
-
onAddChild: (path: (string | number)[], newChild: any) => void
|
|
171
|
-
onRemoveChild: (path: (string | number)[], index: number) => void
|
|
172
|
-
}) {
|
|
173
|
-
if (!node || typeof node !== 'object' || !node.type) {
|
|
174
|
-
console.warn('RecursivePropEditor: node is not a valid preview node', { node, path, previewSpec })
|
|
175
|
-
return null
|
|
176
|
-
}
|
|
177
|
-
if (node?.type === 'text') {
|
|
178
|
-
return (
|
|
179
|
-
<PktPreviewPropEditor
|
|
180
|
-
propKey="text"
|
|
181
|
-
props={{ text: typeof node === 'string' ? node : node.children || '' }}
|
|
182
|
-
spec={{ type: 'longstring', label: 'Tekst' }}
|
|
183
|
-
handleChange={(_, v) => {
|
|
184
|
-
onChange([...path, 'children'], v)
|
|
185
|
-
}}
|
|
186
|
-
/>
|
|
187
|
-
)
|
|
188
|
-
}
|
|
189
|
-
const allowedChildren = Array.isArray(previewSpec.children) ? previewSpec.children : []
|
|
190
|
-
const { props = {}, children: rawChildren = [], type } = node
|
|
191
|
-
const children = normalizeChildren(rawChildren)
|
|
192
|
-
const specKey = node.spec || getSpecKeyForNode(node, previewSpec)
|
|
193
|
-
const spec = specs[specKey as string]
|
|
194
|
-
if (!spec) {
|
|
195
|
-
console.warn(`No spec found for type "${type}" with key "${specKey}"`, { node, props, specs, previewSpec })
|
|
196
|
-
return null // No spec found for this type
|
|
197
|
-
}
|
|
198
|
-
const propKeys = spec.props ? Object.keys(spec.props) : Object.keys(props)
|
|
199
|
-
if (propKeys.length === 0 && children.length === 0) {
|
|
200
|
-
console.warn(`No props or children defined for type "${type}" with spec key "${specKey}"`)
|
|
201
|
-
return null // No props or children to edit
|
|
202
|
-
}
|
|
203
|
-
function applyDefaults(defaults: any, n: number) {
|
|
204
|
-
const result: any = {}
|
|
205
|
-
for (const key in defaults) {
|
|
206
|
-
const val = defaults[key]
|
|
207
|
-
if (typeof val === 'string') {
|
|
208
|
-
result[key] = val.replace(/\$n/g, String(n + 1))
|
|
209
|
-
} else {
|
|
210
|
-
result[key] = val
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return result
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Group prop keys by category
|
|
217
|
-
const categoryOrder = Object.keys(CATEGORIES)
|
|
218
|
-
const groupedProps: Record<string, string[]> = {}
|
|
219
|
-
|
|
220
|
-
propKeys.forEach((key) => {
|
|
221
|
-
const cat = (spec.props?.[key] as TPropObject)?.category || 'other'
|
|
222
|
-
if (!groupedProps[cat]) groupedProps[cat] = []
|
|
223
|
-
groupedProps[cat].push(key)
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
// Sort categories by CATEGORIES order
|
|
227
|
-
let sortedCategories = categoryOrder
|
|
228
|
-
.filter((cat) => groupedProps[cat]?.length)
|
|
229
|
-
.concat(Object.keys(groupedProps).filter((cat) => !categoryOrder.includes(cat)))
|
|
230
|
-
|
|
231
|
-
// Find allowed text child definition (if any)
|
|
232
|
-
const allowedTextChild = allowedChildren.find((c: any) => c.type === 'text')
|
|
233
|
-
// Find existing text child (if any)
|
|
234
|
-
const textChildIndex = children.findIndex((c) => c.type === 'text')
|
|
235
|
-
const textChild = textChildIndex !== -1 ? children[textChildIndex] : null
|
|
236
|
-
|
|
237
|
-
// Ensure 'contents' is present if there's a textChild
|
|
238
|
-
if (textChild && !sortedCategories.includes('contents')) {
|
|
239
|
-
sortedCategories = ['contents', ...sortedCategories]
|
|
240
|
-
if (!groupedProps['contents']) groupedProps['contents'] = []
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return (
|
|
244
|
-
<>
|
|
245
|
-
<PktHeading level={2} size="xsmall">
|
|
246
|
-
{type}
|
|
247
|
-
</PktHeading>
|
|
248
|
-
<PktAccordion compact skin="outlined">
|
|
249
|
-
{sortedCategories.map((cat, index) => (
|
|
250
|
-
<PktAccordionItem
|
|
251
|
-
key={cat}
|
|
252
|
-
title={CATEGORIES[cat as keyof typeof CATEGORIES] || cat}
|
|
253
|
-
id={`cat-${cat}`}
|
|
254
|
-
isOpen={index === 0}
|
|
255
|
-
>
|
|
256
|
-
<div className="pkt-preview__options">
|
|
257
|
-
{groupedProps[cat].map((key) => {
|
|
258
|
-
const propSpec = (spec.props?.[key] || {}) as TPropObject
|
|
259
|
-
// Check for showIf condition
|
|
260
|
-
if (propSpec.showIf) {
|
|
261
|
-
// All conditions in showIf must be met
|
|
262
|
-
const shouldShow = Object.entries(propSpec.showIf).every(([depKey, depValue]) => {
|
|
263
|
-
if (Array.isArray(depValue)) {
|
|
264
|
-
return depValue.includes(props[depKey])
|
|
265
|
-
}
|
|
266
|
-
return props[depKey] === depValue
|
|
267
|
-
})
|
|
268
|
-
if (!shouldShow) return null
|
|
269
|
-
}
|
|
270
|
-
return (
|
|
271
|
-
<PktPreviewPropEditor
|
|
272
|
-
key={key}
|
|
273
|
-
propKey={key}
|
|
274
|
-
props={props}
|
|
275
|
-
spec={propSpec}
|
|
276
|
-
handleChange={(k, v) => {
|
|
277
|
-
const valueToStore =
|
|
278
|
-
propSpec.displayAsFalse && typeof v === 'boolean' ? { value: v, displayAsFalse: true } : v
|
|
279
|
-
onChange([...path, 'props', key], valueToStore)
|
|
280
|
-
}}
|
|
281
|
-
/>
|
|
282
|
-
)
|
|
283
|
-
})}
|
|
284
|
-
{/* If this is the contents category, and text child is allowed, render its editor here */}
|
|
285
|
-
{cat === 'contents' && allowedTextChild && textChild && (
|
|
286
|
-
<PktPreviewPropEditor
|
|
287
|
-
propKey="text"
|
|
288
|
-
props={{ text: typeof textChild === 'string' ? textChild : textChild.children || '' }}
|
|
289
|
-
spec={{ type: 'longstring', name: allowedTextChild.name || 'Tekst' }}
|
|
290
|
-
handleChange={(_, v) => {
|
|
291
|
-
onChange([...path, 'children', textChildIndex, 'children'], v)
|
|
292
|
-
}}
|
|
293
|
-
/>
|
|
294
|
-
)}
|
|
295
|
-
</div>
|
|
296
|
-
</PktAccordionItem>
|
|
297
|
-
))}
|
|
298
|
-
</PktAccordion>
|
|
299
|
-
{Array.isArray(children) && children.length > 0 && (
|
|
300
|
-
<>
|
|
301
|
-
{children.some((child) => child.type !== 'text') && <h2 className="pkt-inputwrapper__label">Innhold</h2>}
|
|
302
|
-
<PktAccordion compact skin="outlined">
|
|
303
|
-
{children.map((child, i) => {
|
|
304
|
-
const allowedChildren = getAllowedChildren(previewSpec)
|
|
305
|
-
const childPreviewSpec = allowedChildren.find((c) => c.type === child.type) || previewSpec
|
|
306
|
-
// text child as string
|
|
307
|
-
if (child.type === 'text') {
|
|
308
|
-
return
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// non-text children
|
|
312
|
-
return (
|
|
313
|
-
<PktAccordionItem
|
|
314
|
-
key={child.props?.id || `child-${i}`}
|
|
315
|
-
title={`${child.props?.id || `${i}`} (${child.type})`}
|
|
316
|
-
id={`child-${i}`}
|
|
317
|
-
>
|
|
318
|
-
<div className="pkt-preview__object-editor">
|
|
319
|
-
<RecursivePropEditor
|
|
320
|
-
node={child}
|
|
321
|
-
path={[...path, 'children', i]}
|
|
322
|
-
previewSpec={childPreviewSpec}
|
|
323
|
-
specs={specs}
|
|
324
|
-
onChange={onChange}
|
|
325
|
-
onAddChild={onAddChild}
|
|
326
|
-
onRemoveChild={onRemoveChild}
|
|
327
|
-
/>
|
|
328
|
-
<div className="pkt-preview__remove-object">
|
|
329
|
-
<PktButton
|
|
330
|
-
onClick={() => {
|
|
331
|
-
confirm(`Er du sikker på at du vil fjerne ${child.type} ${child.props?.id || `#${i + 1}`}`) &&
|
|
332
|
-
onRemoveChild([...path, 'children'], i)
|
|
333
|
-
}}
|
|
334
|
-
style={{ marginTop: 8 }}
|
|
335
|
-
iconName="trash-can"
|
|
336
|
-
skin="tertiary"
|
|
337
|
-
size="small"
|
|
338
|
-
variant="icon-only"
|
|
339
|
-
>
|
|
340
|
-
Fjern {child.type}
|
|
341
|
-
</PktButton>
|
|
342
|
-
</div>
|
|
343
|
-
</div>
|
|
344
|
-
</PktAccordionItem>
|
|
345
|
-
)
|
|
346
|
-
})}
|
|
347
|
-
</PktAccordion>
|
|
348
|
-
</>
|
|
349
|
-
)}
|
|
350
|
-
{allowedChildren.length > 0 &&
|
|
351
|
-
allowedChildren
|
|
352
|
-
.filter((childDef: any) => childDef.type !== 'text')
|
|
353
|
-
.map((childDef: any, i: number) => (
|
|
354
|
-
<PktButton
|
|
355
|
-
key={childDef.type}
|
|
356
|
-
skin="primary"
|
|
357
|
-
size="small"
|
|
358
|
-
variant="icon-left"
|
|
359
|
-
iconName="plus-circle-fill"
|
|
360
|
-
style={{ width: '100%', marginTop: 8 }}
|
|
361
|
-
onClick={() => {
|
|
362
|
-
const n = Array.isArray(children) ? children.length : 0
|
|
363
|
-
const newChild = {
|
|
364
|
-
type: childDef.type,
|
|
365
|
-
props: applyDefaults(childDef.defaults || {}, n),
|
|
366
|
-
children: childDef.slot || '',
|
|
367
|
-
id: `child-${Date.now()}`,
|
|
368
|
-
}
|
|
369
|
-
onAddChild(path, newChild)
|
|
370
|
-
}}
|
|
371
|
-
>
|
|
372
|
-
Legg til {childDef.name || childDef.type}
|
|
373
|
-
</PktButton>
|
|
374
|
-
))}
|
|
375
|
-
</>
|
|
376
|
-
)
|
|
377
|
-
})
|
|
378
|
-
|
|
379
|
-
export const PktPreviewWithJson: React.FC<PreviewProps> = ({ specs, previewJson, fullWidth }) => {
|
|
380
|
-
const [tree, setTree] = useState(attachSpecStrings(previewJson.preview, previewJson))
|
|
381
|
-
const [mode, setMode] = useState<'light' | 'dark'>('light')
|
|
382
|
-
const [previewHtml, setPreviewHtml] = useState('')
|
|
383
|
-
const [previewKey, setPreviewKey] = useState(0)
|
|
384
|
-
const panelRef = useRef<HTMLDialogElement>(null)
|
|
385
|
-
const previewComponent = useRef<HTMLDivElement>(null)
|
|
386
|
-
|
|
387
|
-
useEffect(() => {
|
|
388
|
-
setPreviewHtml(previewComponent.current?.innerHTML ?? '')
|
|
389
|
-
if (previewJson.rerender) {
|
|
390
|
-
setPreviewKey((k) => k + 1)
|
|
391
|
-
}
|
|
392
|
-
}, [tree])
|
|
393
|
-
|
|
394
|
-
const handleEditorChange = useCallback(
|
|
395
|
-
(path: (string | number)[], value: any) => {
|
|
396
|
-
setTree(attachSpecStrings(JSON.parse(JSON.stringify(updateTree(tree, path, value))), previewJson))
|
|
397
|
-
},
|
|
398
|
-
[tree],
|
|
399
|
-
)
|
|
400
|
-
|
|
401
|
-
// Add a child to a node at a given path
|
|
402
|
-
const handleAddChild = useCallback((path: (string | number)[], newChild: any) => {
|
|
403
|
-
setTree((prevTree: any) => {
|
|
404
|
-
const parent = path.length === 0 ? prevTree : path.reduce((acc, key) => acc[key], prevTree)
|
|
405
|
-
const updatedChildren = Array.isArray(parent.children) ? [...parent.children, newChild] : [newChild]
|
|
406
|
-
return attachSpecStrings(
|
|
407
|
-
JSON.parse(JSON.stringify(updateTree(prevTree, [...path, 'children'], updatedChildren))),
|
|
408
|
-
previewJson,
|
|
409
|
-
)
|
|
410
|
-
})
|
|
411
|
-
}, [])
|
|
412
|
-
|
|
413
|
-
// Remove a child at a given path (path points to the children array, index is the child to remove)
|
|
414
|
-
const handleRemoveChild = useCallback((path: (string | number)[], index: number) => {
|
|
415
|
-
setTree((prevTree: any) => {
|
|
416
|
-
const parent = path.length === 0 ? prevTree : path.reduce((acc, key) => acc[key], prevTree)
|
|
417
|
-
if (!Array.isArray(parent)) return prevTree
|
|
418
|
-
const updatedChildren = parent.filter((_: any, i: number) => i !== index)
|
|
419
|
-
return attachSpecStrings(
|
|
420
|
-
JSON.parse(JSON.stringify(updateTree(prevTree, [...path], updatedChildren))),
|
|
421
|
-
previewJson,
|
|
422
|
-
)
|
|
423
|
-
})
|
|
424
|
-
}, [])
|
|
425
|
-
|
|
426
|
-
return (
|
|
427
|
-
<div className="pkt-preview">
|
|
428
|
-
<div className="pkt-preview__component-container">
|
|
429
|
-
<div
|
|
430
|
-
className={`pkt-preview__component ${fullWidth ? 'pkt-preview__component--fullwidth' : ''}`}
|
|
431
|
-
data-mode={mode}
|
|
432
|
-
>
|
|
433
|
-
<div key={previewJson.rerender ? previewKey : undefined} ref={previewComponent}>
|
|
434
|
-
{renderFromSpec(tree)}
|
|
435
|
-
</div>
|
|
436
|
-
</div>
|
|
437
|
-
<div className="pkt-preview__panel" style={{ minWidth: 320, maxWidth: 400 }}>
|
|
438
|
-
<div className="pkt-preview__panel__options">
|
|
439
|
-
{previewJson.previewProps.map((key: string) => (
|
|
440
|
-
<PktPreviewPropEditor
|
|
441
|
-
key={key}
|
|
442
|
-
propKey={key}
|
|
443
|
-
hideKey
|
|
444
|
-
props={tree.props}
|
|
445
|
-
spec={specs[previewJson.spec]?.props?.[key] || {}}
|
|
446
|
-
handleChange={(k, v) => {
|
|
447
|
-
// If displayAsFalse is set in the spec, always store as object
|
|
448
|
-
const spec = specs[previewJson.spec]?.props?.[key] || {}
|
|
449
|
-
const valueToStore =
|
|
450
|
-
(spec as TPropObject).displayAsFalse && typeof v === 'boolean'
|
|
451
|
-
? { value: v, displayAsFalse: true }
|
|
452
|
-
: v
|
|
453
|
-
setTree(
|
|
454
|
-
attachSpecStrings(
|
|
455
|
-
JSON.parse(JSON.stringify(updateTree(tree, ['props', key], valueToStore))),
|
|
456
|
-
previewJson,
|
|
457
|
-
),
|
|
458
|
-
)
|
|
459
|
-
}}
|
|
460
|
-
/>
|
|
461
|
-
))}
|
|
462
|
-
{specs[previewJson.spec]['dark-mode'] && (
|
|
463
|
-
<div className="pkt-preview__mode">
|
|
464
|
-
<PktCheckbox
|
|
465
|
-
id="mode"
|
|
466
|
-
label="Mørk modus"
|
|
467
|
-
type="checkbox"
|
|
468
|
-
checked={mode === 'dark'}
|
|
469
|
-
onChange={(e) => setMode(e.target.checked ? 'dark' : 'light')}
|
|
470
|
-
labelPosition="right"
|
|
471
|
-
isSwitch
|
|
472
|
-
/>
|
|
473
|
-
</div>
|
|
474
|
-
)}
|
|
475
|
-
</div>
|
|
476
|
-
<div className="pkt-preview__panel__button">
|
|
477
|
-
<PktButton
|
|
478
|
-
skin="secondary"
|
|
479
|
-
variant="icon-right"
|
|
480
|
-
iconName="crane"
|
|
481
|
-
className="pkt-preview__open-panel"
|
|
482
|
-
onClick={() => {
|
|
483
|
-
panelRef.current && panelRef.current.showModal()
|
|
484
|
-
}}
|
|
485
|
-
>
|
|
486
|
-
Åpne kodebygger
|
|
487
|
-
</PktButton>
|
|
488
|
-
<PktModal
|
|
489
|
-
variant="drawer"
|
|
490
|
-
drawerPosition="right"
|
|
491
|
-
size="small"
|
|
492
|
-
ref={panelRef}
|
|
493
|
-
transparentBackdrop
|
|
494
|
-
closeOnBackdropClick
|
|
495
|
-
>
|
|
496
|
-
<div className="pkt-preview__options">
|
|
497
|
-
<PktHeading level={1} size="medium">
|
|
498
|
-
Kodebygger
|
|
499
|
-
</PktHeading>
|
|
500
|
-
|
|
501
|
-
<RecursivePropEditor
|
|
502
|
-
node={tree}
|
|
503
|
-
previewSpec={previewJson}
|
|
504
|
-
specs={specs}
|
|
505
|
-
onChange={handleEditorChange}
|
|
506
|
-
onAddChild={handleAddChild}
|
|
507
|
-
onRemoveChild={handleRemoveChild}
|
|
508
|
-
/>
|
|
509
|
-
</div>
|
|
510
|
-
</PktModal>
|
|
511
|
-
</div>
|
|
512
|
-
</div>
|
|
513
|
-
</div>
|
|
514
|
-
<PktPreviewCode tree={tree} specs={specs} html={previewHtml} />
|
|
515
|
-
</div>
|
|
516
|
-
)
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
PktPreviewWithJson.displayName = 'PktPreviewWithJson'
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export interface ISpecObject {
|
|
2
|
-
name?: string
|
|
3
|
-
'css-class'?: string
|
|
4
|
-
'dark-mode'?: boolean
|
|
5
|
-
isElement?: boolean
|
|
6
|
-
props?: Record<string, TPropObject | string[] | number[]>
|
|
7
|
-
events?: Record<string, TPropObject>
|
|
8
|
-
slots?: Record<string, any>
|
|
9
|
-
default?: any
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface IPropEditorProps {
|
|
13
|
-
propKey: string
|
|
14
|
-
spec: any
|
|
15
|
-
props: any
|
|
16
|
-
hideKey?: boolean
|
|
17
|
-
handleChange: (key: string, value: any, displayAsFalse?: boolean) => void
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export type TPropObject = {
|
|
21
|
-
name?: string
|
|
22
|
-
description?: string
|
|
23
|
-
type?: string | string[] | number | number[]
|
|
24
|
-
variant?: string
|
|
25
|
-
converter?: string
|
|
26
|
-
reflect?: boolean
|
|
27
|
-
default?: any
|
|
28
|
-
previewDefault?: any
|
|
29
|
-
items?: TPropObject
|
|
30
|
-
displayAsFalse?: boolean
|
|
31
|
-
category?: string
|
|
32
|
-
showIf?: Record<string, any>
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export type TPreviewTreeNode = {
|
|
36
|
-
type: string
|
|
37
|
-
spec?: string
|
|
38
|
-
props?: Record<string, any>
|
|
39
|
-
children?: any
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export type TSpecs = Record<string, ISpecObject>
|