@oslokommune/punkt-react 13.6.15 → 13.7.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 (67) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/index.d.ts +38 -74
  3. package/dist/punkt-react.es.js +5358 -35971
  4. package/dist/punkt-react.umd.js +418 -548
  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/datepicker/Datepicker.test.tsx +393 -0
  15. package/src/components/footer/Footer.tsx +6 -5
  16. package/src/components/footerSimple/FooterSimple.tsx +4 -3
  17. package/src/components/icon/Icon.test.tsx +6 -19
  18. package/src/components/index.ts +0 -2
  19. package/src/components/input/Input.tsx +4 -3
  20. package/src/components/radio/RadioButton.tsx +3 -2
  21. package/src/components/searchinput/SearchInput.tsx +3 -3
  22. package/src/components/stepper/Stepper.tsx +6 -6
  23. package/src/components/table/Table.tsx +2 -1
  24. package/src/components/table/TableBody.tsx +2 -1
  25. package/src/components/table/TableData.tsx +2 -1
  26. package/src/components/table/TableDataCell.tsx +2 -1
  27. package/src/components/table/TableHeader.tsx +2 -1
  28. package/src/components/table/TableHeaderCell.tsx +2 -1
  29. package/src/components/table/TableRow.tsx +2 -1
  30. package/src/components/tag/Tag.tsx +2 -1
  31. package/src/components/preview/Preview.tsx +0 -274
  32. package/src/components/preview/PreviewCode.tsx +0 -118
  33. package/src/components/preview/PreviewPropEditor.tsx +0 -266
  34. package/src/components/preview/PreviewSpecs.tsx +0 -125
  35. package/src/components/preview/PreviewWithJson.tsx +0 -519
  36. package/src/components/preview/preview-types.ts +0 -42
  37. package/src/components/preview/preview-utils.ts +0 -226
  38. package/src/components/preview/previewJson/accordion.json +0 -84
  39. package/src/components/preview/previewJson/alert.json +0 -27
  40. package/src/components/preview/previewJson/backlink.json +0 -14
  41. package/src/components/preview/previewJson/breadcrumbs.json +0 -17
  42. package/src/components/preview/previewJson/button.json +0 -28
  43. package/src/components/preview/previewJson/card.json +0 -41
  44. package/src/components/preview/previewJson/checkbox.json +0 -21
  45. package/src/components/preview/previewJson/combobox.json +0 -24
  46. package/src/components/preview/previewJson/consent.json +0 -14
  47. package/src/components/preview/previewJson/datepicker.json +0 -14
  48. package/src/components/preview/previewJson/footer-simple.json +0 -17
  49. package/src/components/preview/previewJson/footer.json +0 -29
  50. package/src/components/preview/previewJson/header.json +0 -34
  51. package/src/components/preview/previewJson/icon.json +0 -13
  52. package/src/components/preview/previewJson/input-wrapper.json +0 -39
  53. package/src/components/preview/previewJson/link.json +0 -28
  54. package/src/components/preview/previewJson/linkcard.json +0 -30
  55. package/src/components/preview/previewJson/loader.json +0 -28
  56. package/src/components/preview/previewJson/messagebox.json +0 -28
  57. package/src/components/preview/previewJson/modal.json +0 -65
  58. package/src/components/preview/previewJson/progressbar.json +0 -16
  59. package/src/components/preview/previewJson/radiobutton.json +0 -21
  60. package/src/components/preview/previewJson/searchinput.json +0 -20
  61. package/src/components/preview/previewJson/select.json +0 -73
  62. package/src/components/preview/previewJson/steps.json +0 -72
  63. package/src/components/preview/previewJson/table.json +0 -130
  64. package/src/components/preview/previewJson/tabs.json +0 -33
  65. package/src/components/preview/previewJson/tag.json +0 -26
  66. package/src/components/preview/previewJson/textarea.json +0 -18
  67. package/src/components/preview/previewJson/textinput.json +0 -18
@@ -1,11 +1,12 @@
1
1
  'use client'
2
2
 
3
- import React from 'react'
3
+ import { FC } from 'react'
4
+ import type { HTMLAttributes } from 'react'
4
5
 
5
6
  import { PktIcon } from '../icon/Icon'
6
7
  import { PktConsent } from '../consent/Consent'
7
8
 
8
- export interface IPktFooterSimple extends React.HTMLAttributes<HTMLDivElement> {
9
+ export interface IPktFooterSimple extends HTMLAttributes<HTMLDivElement> {
9
10
  links?: Array<{
10
11
  href: string
11
12
  text: string
@@ -25,7 +26,7 @@ export interface IPktFooterSimple extends React.HTMLAttributes<HTMLDivElement> {
25
26
  onToggleConsent?: (e: CustomEvent) => void
26
27
  }
27
28
 
28
- export const PktFooterSimple: React.FC<IPktFooterSimple> = ({
29
+ export const PktFooterSimple: FC<IPktFooterSimple> = ({
29
30
  links = [],
30
31
  openLinksInNewTab = false,
31
32
  personvernOgInfoLink = 'https://www.oslo.kommune.no/personvern-og-informasjonskapsler/',
@@ -1,7 +1,6 @@
1
1
  import '@testing-library/jest-dom'
2
2
 
3
3
  import { render, screen, waitFor } from '../../utils/test-utils'
4
- import React from 'react'
5
4
 
6
5
  import { PktIcon } from './Icon'
7
6
 
@@ -11,26 +10,14 @@ declare global {
11
10
  pktIconPath: string
12
11
  }
13
12
  }
14
- window.pktFetch = (file = 'filnavn.svg') =>
15
- Promise.resolve({
16
- ok: true,
17
- text: () => Promise.resolve(`<svg role="img" data-testid="icon" data-filename=${file}>Her er SVGen</svg>`),
18
- })
19
- window.pktIconPath = 'sti'
20
13
 
21
14
  describe('PktIcon', () => {
22
- const localStorageMock = (() => {
23
- const store: { [key: string]: string } = {}
24
- return {
25
- getItem: function (key: string) {
26
- return store[key] || null
27
- },
28
- setItem: function (key: string, value: string) {
29
- store[key] = value.toString()
30
- },
31
- }
32
- })()
33
- Object.defineProperty(global, 'localStorage', { value: localStorageMock })
15
+ window.pktFetch = (file = 'filnavn.svg') =>
16
+ Promise.resolve({
17
+ ok: true,
18
+ text: () => Promise.resolve(`<svg role="img" data-testid="icon" data-filename=${file}>Her er SVGen</svg>`),
19
+ })
20
+ window.pktIconPath = 'sti'
34
21
 
35
22
  describe('with default fetcher', () => {
36
23
  test('fetches SVG with default fetcher', async () => {
@@ -22,8 +22,6 @@ export { PktLinkCard } from './linkcard/LinkCard'
22
22
  export { PktLoader } from './loader/Loader'
23
23
  export { PktMessagebox } from './messagebox/Messagebox'
24
24
  export { PktModal } from './modal/Modal'
25
- export { PktPreview } from './preview/Preview'
26
- export { PktPreviewWithJson } from './preview/PreviewWithJson'
27
25
  export { PktProgressbar } from './progressbar/Progressbar'
28
26
  export { PktRadioButton } from './radio/RadioButton'
29
27
  export { PktSearchInput } from './searchinput/SearchInput'
@@ -1,8 +1,9 @@
1
1
  'use client'
2
2
 
3
- import React, { ForwardedRef } from 'react'
3
+ import { ForwardedRef, forwardRef } from 'react'
4
+ import type { InputHTMLAttributes } from 'react'
4
5
 
5
- interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
6
+ interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
6
7
  /** The input's label */
7
8
  label?: string
8
9
  /** The input's id */
@@ -21,7 +22,7 @@ interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
21
22
  * <Input label="First name" id="firstName" validationMessage="First name is required" />
22
23
  *
23
24
  */
24
- export const PktInput = React.forwardRef(
25
+ export const PktInput = forwardRef(
25
26
  ({ label, id, children, ...props }: InputProps, ref: ForwardedRef<HTMLInputElement>): React.ReactElement => {
26
27
  return (
27
28
  <div className="pkt-form-group">
@@ -1,6 +1,7 @@
1
- import React, { ChangeEventHandler, ForwardedRef, forwardRef, ReactNode } from 'react'
1
+ import { ChangeEventHandler, ForwardedRef, forwardRef, ReactNode } from 'react'
2
+ import type { InputHTMLAttributes } from 'react'
2
3
 
3
- export interface IPktRadioButton extends React.InputHTMLAttributes<HTMLInputElement> {
4
+ export interface IPktRadioButton extends InputHTMLAttributes<HTMLInputElement> {
4
5
  id: string
5
6
  name: string
6
7
  label: string
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import React, { ChangeEvent, forwardRef, HTMLProps, ReactNode } from 'react'
3
+ import { ChangeEvent, forwardRef, HTMLProps, ReactNode, createElement } from 'react'
4
4
 
5
5
  import { PktButton } from '../button/Button'
6
6
 
@@ -33,7 +33,7 @@ interface ISearchForm extends ISearch {
33
33
  }
34
34
 
35
35
  interface ISearchInput extends ISearch {
36
- onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
36
+ onChange: (event: ChangeEvent<HTMLInputElement>) => void
37
37
  disabled?: boolean
38
38
  }
39
39
 
@@ -145,7 +145,7 @@ export const PktSearchInput = forwardRef<HTMLInputElement, ISearchInput | ISearc
145
145
  <ul id={`${id}-suggestions`} className="pkt-searchinput__suggestions" aria-live="assertive">
146
146
  {suggestions.map((suggestion, index) => (
147
147
  <li key={`search-suggestion-${index}`}>
148
- {React.createElement(
148
+ {createElement(
149
149
  suggestion.href ? 'a' : suggestion.onClick ? 'button' : 'div',
150
150
  {
151
151
  href: suggestion.href,
@@ -1,11 +1,11 @@
1
1
  'use client'
2
2
 
3
- import { ReactElement, Ref, forwardRef } from 'react'
3
+ import { ReactElement, Ref, forwardRef, Children, isValidElement, cloneElement } from 'react'
4
+ import type { HTMLAttributes } from 'react'
4
5
  import { IPktStep } from './Step'
5
6
  import classNames from 'classnames'
6
- import React from 'react'
7
7
 
8
- export interface IPktStepper extends React.HTMLAttributes<HTMLOListElement> {
8
+ export interface IPktStepper extends HTMLAttributes<HTMLOListElement> {
9
9
  /**
10
10
  * The index of the current active step
11
11
  */
@@ -55,9 +55,9 @@ export const PktStepper = forwardRef(
55
55
  orientation === 'horizontal' ? 'pkt-stepper--horizontal' : 'pkt-stepper--vertical',
56
56
  )
57
57
 
58
- const childrenWithProps = React.Children.map(children, (child, index) => {
59
- if (React.isValidElement(child)) {
60
- return React.cloneElement(child, {
58
+ const childrenWithProps = Children.map(children, (child, index) => {
59
+ if (isValidElement(child)) {
60
+ return cloneElement(child, {
61
61
  className: classNames(child.props.className, {
62
62
  'pkt-step--hideStep': hideNonActiveSteps && index !== activeStep,
63
63
  'pkt-step--hideContent': hideNonActiveStepsContent && index !== activeStep,
@@ -2,10 +2,11 @@
2
2
 
3
3
  import classNames from 'classnames'
4
4
  import * as React from 'react'
5
+ import type { HTMLAttributes } from 'react'
5
6
 
6
7
  export type TTableSkin = 'basic' | 'zebra-blue'
7
8
 
8
- interface ITableProps extends React.HTMLAttributes<HTMLTableElement> {
9
+ interface ITableProps extends HTMLAttributes<HTMLTableElement> {
9
10
  compact?: boolean
10
11
  skin?: 'basic' | 'zebra-blue'
11
12
  responsiveView?: boolean
@@ -2,8 +2,9 @@
2
2
 
3
3
  import classNames from 'classnames'
4
4
  import * as React from 'react'
5
+ import type { HTMLAttributes } from 'react'
5
6
 
6
- interface ITableBodyProps extends React.HTMLAttributes<HTMLTableSectionElement> {
7
+ interface ITableBodyProps extends HTMLAttributes<HTMLTableSectionElement> {
7
8
  className?: string
8
9
  children: React.ReactNode
9
10
  }
@@ -2,8 +2,9 @@
2
2
 
3
3
  import classNames from 'classnames'
4
4
  import * as React from 'react'
5
+ import type { HTMLAttributes } from 'react'
5
6
 
6
- interface TableDataProps extends React.HTMLAttributes<HTMLTableCellElement> {
7
+ interface TableDataProps extends HTMLAttributes<HTMLTableCellElement> {
7
8
  className?: string
8
9
  children: React.ReactNode | React.ReactNode[]
9
10
  }
@@ -2,8 +2,9 @@
2
2
 
3
3
  import classNames from 'classnames'
4
4
  import * as React from 'react'
5
+ import type { HTMLAttributes } from 'react'
5
6
 
6
- interface ITableDataCellProps extends React.HTMLAttributes<HTMLTableCellElement> {
7
+ interface ITableDataCellProps extends HTMLAttributes<HTMLTableCellElement> {
7
8
  className?: string
8
9
  children?: React.ReactNode
9
10
  dataLabel?: string
@@ -2,8 +2,9 @@
2
2
 
3
3
  import classNames from 'classnames'
4
4
  import * as React from 'react'
5
+ import type { HTMLAttributes } from 'react'
5
6
 
6
- interface ITableHeaderProps extends React.HTMLAttributes<HTMLTableSectionElement> {
7
+ interface ITableHeaderProps extends HTMLAttributes<HTMLTableSectionElement> {
7
8
  className?: string
8
9
  children: React.ReactNode
9
10
  }
@@ -2,8 +2,9 @@
2
2
 
3
3
  import classNames from 'classnames'
4
4
  import * as React from 'react'
5
+ import type { HTMLAttributes } from 'react'
5
6
 
6
- interface ITableHeaderCellProps extends React.HTMLAttributes<HTMLTableCellElement> {
7
+ interface ITableHeaderCellProps extends HTMLAttributes<HTMLTableCellElement> {
7
8
  className?: string
8
9
  children: React.ReactNode
9
10
  }
@@ -2,8 +2,9 @@
2
2
 
3
3
  import classNames from 'classnames'
4
4
  import * as React from 'react'
5
+ import type { HTMLAttributes } from 'react'
5
6
 
6
- interface ITableRowProps extends React.HTMLAttributes<HTMLTableRowElement> {
7
+ interface ITableRowProps extends HTMLAttributes<HTMLTableRowElement> {
7
8
  className?: string
8
9
  children: React.ReactNode
9
10
  }
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
 
3
- import React, { FC, ForwardedRef, forwardRef, ReactElement } from 'react'
3
+ import { FC, ForwardedRef, forwardRef, ReactElement } from 'react'
4
+ import * as React from 'react'
4
5
  import { createComponent, EventName } from '@lit/react'
5
6
  import { PktTag as PktElTag } from '@oslokommune/punkt-elements'
6
7
  import type { PktElType, PktElConstructor } from '@/interfaces/IPktElements'
@@ -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
- }