@oslokommune/punkt-react 12.33.0 → 12.34.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oslokommune/punkt-react",
3
- "version": "12.33.0",
3
+ "version": "12.34.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,7 +38,7 @@
38
38
  "dependencies": {
39
39
  "@lit-labs/ssr-dom-shim": "^1.2.1",
40
40
  "@lit/react": "^1.0.5",
41
- "@oslokommune/punkt-elements": "^12.33.0",
41
+ "@oslokommune/punkt-elements": "^12.34.0",
42
42
  "angular-html-parser": "^6.0.2",
43
43
  "html-format": "^1.1.7",
44
44
  "prettier": "^3.3.3",
@@ -49,7 +49,7 @@
49
49
  "devDependencies": {
50
50
  "@babel/plugin-proposal-private-property-in-object": "^7.18.6",
51
51
  "@oslokommune/punkt-assets": "^12.30.1",
52
- "@oslokommune/punkt-css": "^12.33.0",
52
+ "@oslokommune/punkt-css": "^12.34.0",
53
53
  "@testing-library/jest-dom": "^6.5.0",
54
54
  "@testing-library/react": "^16.0.1",
55
55
  "@testing-library/user-event": "^14.5.2",
@@ -112,5 +112,5 @@
112
112
  "url": "https://github.com/oslokommune/punkt/issues"
113
113
  },
114
114
  "license": "MIT",
115
- "gitHead": "892989f59c2dd0af07cb234344ffdd828a652c7c"
115
+ "gitHead": "4ab2b5b6508cf1749c9b1a66d92c6d214f737b26"
116
116
  }
@@ -2,7 +2,7 @@ import '@testing-library/jest-dom'
2
2
  import { axe, toHaveNoViolations } from 'jest-axe'
3
3
  import { PktAccordion } from './Accordion'
4
4
  import { PktAccordionItem } from './AccordionItem'
5
- import { render, screen } from '@testing-library/react'
5
+ import { render, screen, waitFor } from '@testing-library/react'
6
6
  import React, { createRef } from 'react'
7
7
 
8
8
  expect.extend(toHaveNoViolations)
@@ -20,24 +20,14 @@ describe('PktAccordion', () => {
20
20
  // Assert that the Accordion component renders without throwing any errors
21
21
  })
22
22
 
23
- test('ref works correctly', async () => {
24
- const ref = createRef<HTMLDivElement>()
25
- const { unmount } = render(<PktAccordion ref={ref}>Alert text</PktAccordion>)
26
- await window.customElements.whenDefined('pkt-icon')
27
- expect(ref.current).toBeInstanceOf(HTMLDivElement)
28
-
29
- unmount()
30
- expect(ref.current).toBe(null)
31
- })
32
-
33
23
  test('renders children', async () => {
34
24
  const mockToggleOpen = jest.fn()
35
25
  render(
36
26
  <PktAccordion>
37
- <PktAccordionItem title="Title 1" id="item1" toggleProps={{ isOpen: false, onToggleClick: mockToggleOpen }}>
27
+ <PktAccordionItem title="Title 1" id="item1" onClick={mockToggleOpen}>
38
28
  Content 1
39
29
  </PktAccordionItem>
40
- <PktAccordionItem title="Title 2" id="item2" toggleProps={{ isOpen: false, onToggleClick: mockToggleOpen }}>
30
+ <PktAccordionItem title="Title 2" id="item2" onClick={mockToggleOpen}>
41
31
  Content 2
42
32
  </PktAccordionItem>
43
33
  </PktAccordion>,
@@ -51,38 +41,78 @@ describe('PktAccordion', () => {
51
41
  expect(screen.getByText('Content 2')).toBeInTheDocument()
52
42
  })
53
43
 
54
- test('applies compact and skin ', async () => {
55
- render(
44
+ test('applies compact and skin', async () => {
45
+ const ref = createRef<any>()
46
+ const { container } = render(
56
47
  <>
57
48
  <h3 id="accordion-heading">Accordion Heading</h3>
58
- <PktAccordion skin="outlined" compact>
49
+ <PktAccordion ref={ref}>
59
50
  <PktAccordionItem title="Title" id="item1">
60
51
  Content
61
52
  </PktAccordionItem>
62
53
  </PktAccordion>
63
54
  </>,
64
55
  )
65
- await window.customElements.whenDefined('pkt-icon')
66
- // Assert that the Accordion component applies the props correctly
67
- expect(screen.getByTestId('pkt-accordion')).toHaveClass('pkt-accordion--compact')
68
- expect(screen.getByTestId('pkt-accordion')).toHaveClass('pkt-accordion--outlined')
56
+
57
+ // Wait for the element to be defined
58
+ await window.customElements.whenDefined('pkt-accordion')
59
+ await window.customElements.whenDefined('pkt-accordion-item')
60
+
61
+ // Now manually set properties on the element
62
+ if (ref.current) {
63
+ ref.current.skin = 'blue'
64
+ ref.current.compact = true
65
+ await ref.current.updateComplete // Wait for Lit to update
66
+ }
67
+
68
+ const accordion = container.querySelector('pkt-accordion')!
69
+ expect(accordion).toBeInTheDocument()
70
+ expect(accordion).toHaveAttribute('compact') // compact reflected as boolean attribute
71
+ expect(accordion).toHaveAttribute('skin', 'blue')
69
72
  })
73
+ })
70
74
 
71
- describe('accessibility', () => {
72
- it('renders with no wcag errors with axe', async () => {
73
- const { container } = render(
74
- <PktAccordion>
75
- <PktAccordionItem title="Title 1" id="item1">
76
- Content 1
77
- </PktAccordionItem>
78
- <PktAccordionItem title="Title 2" id="item2">
79
- Content 2
80
- </PktAccordionItem>
81
- </PktAccordion>,
82
- )
83
- await window.customElements.whenDefined('pkt-icon')
84
- const results = await axe(container)
85
- expect(results).toHaveNoViolations()
86
- })
75
+ describe('PktAccordionItem', () => {
76
+ test('applies compact and skin', async () => {
77
+ const ref = createRef<any>()
78
+ const { container } = render(
79
+ <>
80
+ <PktAccordionItem ref={ref} title="Title" id="item1" skin="blue" compact>
81
+ Content
82
+ </PktAccordionItem>
83
+ </>,
84
+ )
85
+
86
+ // Wait for the element to be defined
87
+ await window.customElements.whenDefined('pkt-accordion-item')
88
+
89
+ // Now manually set properties on the element
90
+ if (ref.current) {
91
+ ref.current.skin = 'blue'
92
+ ref.current.compact = true
93
+ ref.current.requestUpdate()
94
+ await ref.current.updateComplete
95
+ }
96
+
97
+ const details = container.querySelector('details')!
98
+ expect(details.classList.contains('pkt-accordion-item--blue')).toBe(true)
99
+ })
100
+ })
101
+
102
+ describe('accessibility', () => {
103
+ it('renders with no wcag errors with axe', async () => {
104
+ const { container } = render(
105
+ <PktAccordion>
106
+ <PktAccordionItem title="Title 1" id="item1">
107
+ Content 1
108
+ </PktAccordionItem>
109
+ <PktAccordionItem title="Title 2" id="item2">
110
+ Content 2
111
+ </PktAccordionItem>
112
+ </PktAccordion>,
113
+ )
114
+ await window.customElements.whenDefined('pkt-icon')
115
+ const results = await axe(container)
116
+ expect(results).toHaveNoViolations()
87
117
  })
88
118
  })
@@ -1,36 +1,41 @@
1
- import { ReactNode, Ref, forwardRef } from 'react'
1
+ import { PktElConstructor, PktElType } from '@/interfaces/IPktElements'
2
+ import { createComponent } from '@lit/react'
3
+ import React, { ForwardRefExoticComponent, LegacyRef } from 'react'
4
+ import { FC, ReactNode, Ref, forwardRef } from 'react'
5
+ import { PktAccordion as PktElAccordion } from '@oslokommune/punkt-elements'
6
+ import { TPktAccordionSkin } from '@oslokommune/punkt-elements'
2
7
 
3
- export interface IPktAccordion {
4
- className?: string
5
- children: ReactNode | ReactNode[]
6
- /**
7
- * @default compact: "false"
8
- */
8
+ export interface IPktAccordion extends PktElType {
9
9
  compact?: boolean
10
10
  /**
11
11
  * @default skin: "borderless"
12
12
  */
13
- skin?: 'borderless' | 'outlined' | 'beige' | 'blue'
13
+ skin?: TPktAccordionSkin
14
+ /**
15
+ * @description A unique identifier to connect the accordion with a heading
16
+ */
17
+ ariaLabelledBy?: string
14
18
  /**
15
19
  * @description A unique identifier to connect the accordion with a heading
16
20
  */
21
+ ref?: LegacyRef<HTMLElement>
17
22
  }
18
23
 
19
- export const PktAccordion = forwardRef(
20
- ({ compact = false, skin = 'borderless', className, children }: IPktAccordion, ref: Ref<HTMLDivElement>) => {
21
- const stylingClasses = [
22
- className,
23
- 'pkt-accordion',
24
- compact && `pkt-accordion--compact`,
25
- skin && `pkt-accordion--${skin}`,
26
- ]
27
- .filter(Boolean)
28
- .join(' ')
24
+ const LitComponent: FC<IPktAccordion> = createComponent({
25
+ tagName: 'pkt-accordion',
26
+ elementClass: PktElAccordion as PktElConstructor<HTMLElement>,
27
+ react: React,
28
+ displayName: 'PktAccordion',
29
+ }) as ForwardRefExoticComponent<IPktAccordion>
29
30
 
31
+ export const PktAccordion: FC<IPktAccordion> = forwardRef(
32
+ ({ compact = false, skin = 'borderless', ariaLabelledBy, children, ...props }: IPktAccordion, ref) => {
30
33
  return (
31
- <div data-testid="pkt-accordion" className={stylingClasses} ref={ref}>
32
- {children}
33
- </div>
34
+ <LitComponent data-testid="pkt-accordion" {...props} ref={ref} aria-labelledby={ariaLabelledBy}>
35
+ <div className="pkt-contents">{children}</div>
36
+ </LitComponent>
34
37
  )
35
38
  },
36
39
  )
40
+
41
+ PktAccordion.displayName = 'PktAccordion'
@@ -1,83 +1,38 @@
1
- import React, { LegacyRef, forwardRef, useState } from 'react'
2
- import PktIcon from '../icon/Icon'
3
- import classNames from 'classnames'
4
-
5
- /**
6
- * @param toggleProps
7
- *
8
- * @description Send in an "isOpen" boolean and "onToggleClick" function
9
- * to override the automatic toggling
10
- */
11
-
12
- export interface IPktAccordionToggleProps {
13
- isOpen: boolean
14
- onToggleClick: (e: React.MouseEvent, id: string) => void
15
- }
16
-
17
- export interface IPktAccordionItem {
18
- children?: React.ReactNode | React.ReactNode[]
19
- className?: string
1
+ import React, { FC, ForwardRefExoticComponent, LegacyRef, ReactElement, forwardRef } from 'react'
2
+ import { PktElConstructor, PktElType, PktEventWithTarget } from '@/interfaces/IPktElements'
3
+ import { createComponent, EventName } from '@lit/react'
4
+ import { PktAccordionItem as PktElAccordionItem } from '@oslokommune/punkt-elements'
5
+ import { TPktAccordionSkin } from '@oslokommune/punkt-elements'
6
+
7
+ export interface IPktAccordionItem extends Omit<PktElType, 'onClick'> {
8
+ compact?: boolean
9
+ skin?: TPktAccordionSkin
10
+ title?: string
20
11
  defaultOpen?: boolean
21
- id: string
22
- title: string | React.ReactNode
23
- toggleProps?: IPktAccordionToggleProps
12
+ isOpen?: boolean
13
+ id?: string
14
+ onClick?: (e: PktEventWithTarget) => void
15
+ ref?: LegacyRef<HTMLElement>
24
16
  }
25
17
 
26
- export const PktAccordionItem = forwardRef(
27
- (
28
- { title, className, children, toggleProps, defaultOpen = false, id }: IPktAccordionItem,
29
- ref: LegacyRef<HTMLDetailsElement>,
30
- ) => {
31
- const [isOpen, setIsOpen] = useState<boolean>(defaultOpen)
32
-
33
- const openToggleProp: boolean = toggleProps ? toggleProps.isOpen : isOpen
34
-
35
- /**
36
- * To use the "open" attribute of <details> element correctly,
37
- * we need to return either "open=true" or "open=undefined" as setting "open=false"
38
- * will set the "open" attribute to true as "false" is still a boolean
39
- */
40
-
41
- const returnTrueIfOpen = (isOpen: boolean): boolean | undefined => {
42
- return isOpen ?? undefined
43
- }
44
-
45
- const handleToggleClick = (e: React.MouseEvent, id: string) => {
46
- /**
47
- * e.preventDefault() is important to prevent native default onToggle behavior
48
- * (that the event receives from being a <details> element) as it creates
49
- * inconsistent behaviour toggling the "open" attribute both locally
50
- * and through the React state.
51
- * Read more here: https://github.com/facebook/react/issues/15486#issuecomment-873516817
52
- * */
53
- e.preventDefault()
54
-
55
- if (toggleProps !== undefined) {
56
- toggleProps.onToggleClick(e, id)
57
- }
58
- setIsOpen(!openToggleProp) // use open if it exists, if not use isOpen
59
- }
60
- const accordionItemClass = classNames(className, 'pkt-accordion-item')
18
+ const LitComponent: FC<IPktAccordionItem> = createComponent({
19
+ tagName: 'pkt-accordion-item',
20
+ elementClass: PktElAccordionItem as PktElConstructor<HTMLElement>,
21
+ react: React,
22
+ displayName: 'PktAccordionItem',
23
+ events: {
24
+ onClick: 'click' as EventName<PktEventWithTarget>,
25
+ },
26
+ }) as ForwardRefExoticComponent<IPktAccordionItem>
61
27
 
28
+ export const PktAccordionItem: FC<IPktAccordionItem> = forwardRef(
29
+ ({ children, ...props }: Omit<IPktAccordionItem, 'ref'>, ref): ReactElement => {
62
30
  return (
63
- <details
64
- className={accordionItemClass}
65
- ref={ref}
66
- id={id}
67
- open={toggleProps ? returnTrueIfOpen(toggleProps.isOpen) : returnTrueIfOpen(isOpen)}
68
- >
69
- <summary
70
- className="pkt-accordion-item__title"
71
- id={`pkt-accordion-item-summary-${id}`}
72
- onClick={(e) => handleToggleClick(e, id)}
73
- >
74
- {title}
75
- <PktIcon name="chevron-thin-down" className="pkt-accordion-item__icon" aria-hidden="true" />
76
- </summary>
77
- <div id={`pkt-accordion-item-content-${id}`} role="region" className="pkt-accordion-item__content">
78
- {children}
79
- </div>
80
- </details>
31
+ <LitComponent ref={ref} {...props}>
32
+ <div className="pkt-contents">{children}</div>
33
+ </LitComponent>
81
34
  )
82
35
  },
83
36
  )
37
+
38
+ PktAccordionItem.displayName = 'PktAccordionItem'
@@ -19,7 +19,7 @@ export interface IPktCheckbox extends InputHTMLAttributes<HTMLInputElement> {
19
19
  defaultChecked?: boolean
20
20
  disabled?: boolean
21
21
  value?: string
22
- checkHelptext?: string | React.ReactNode | React.ReactNode[]
22
+ checkHelptext?: string | ReactNode | ReactNode[]
23
23
  isSwitch?: boolean
24
24
  hideLabel?: boolean
25
25
  labelPosition?: 'right' | 'left'
@@ -151,7 +151,7 @@ export const PktSearchInput = forwardRef<HTMLInputElement, ISearchInput | ISearc
151
151
  }`,
152
152
  type: suggestion.onClick ? 'button' : undefined,
153
153
  onClick: () => handleSuggestionClick(suggestion.onClick, index),
154
- onKeyUp: () => handleSuggestionClick(suggestion.onClick, index),
154
+ onKeyUp: (event: { key: string }) => event.key === "Enter" && handleSuggestionClick(suggestion.onClick, index)
155
155
  },
156
156
  <>
157
157
  {suggestion.title && <h3 className="pkt-searchinput__suggestion-title">{suggestion.title}</h3>}