@dhis2-ui/layer 10.16.2 → 10.16.3-alpha.1

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": "@dhis2-ui/layer",
3
- "version": "10.16.2",
3
+ "version": "10.16.3-alpha.1",
4
4
  "description": "UI Layer",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,14 +33,15 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@dhis2/prop-types": "^3.1.2",
36
- "@dhis2-ui/portal": "10.16.2",
37
- "@dhis2/ui-constants": "10.16.2",
36
+ "@dhis2-ui/portal": "10.16.3-alpha.1",
37
+ "@dhis2/ui-constants": "10.16.3-alpha.1",
38
38
  "classnames": "^2.3.1",
39
39
  "prop-types": "^15.7.2"
40
40
  },
41
41
  "files": [
42
42
  "build",
43
- "types"
43
+ "types",
44
+ "src"
44
45
  ],
45
46
  "devDependencies": {
46
47
  "react": "^18.3.1",
@@ -0,0 +1,8 @@
1
+ import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a Layer with children is rendered', () => {
4
+ cy.visitStory('Layer', 'Default')
5
+ })
6
+ Then('the children are visible', () => {
7
+ cy.contains('I am a child').should('be.visible')
8
+ })
@@ -0,0 +1,5 @@
1
+ Feature: The Layer renders children
2
+
3
+ Scenario: A Layer with children
4
+ Given a Layer with children is rendered
5
+ Then the children are visible
@@ -0,0 +1,52 @@
1
+ import { Given, When, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('a Layer with a button below it is rendered', () => {
4
+ cy.visitStory('Layer', 'Blocking')
5
+ })
6
+
7
+ Given('a Layer with a button in it is rendered', () => {
8
+ cy.visitStory('Layer', 'With Click Handler')
9
+ })
10
+
11
+ When('the user clicks the button', () => {
12
+ cy.get('button:contains("Test")').click()
13
+ })
14
+
15
+ When('the user clicks on the layer', () => {
16
+ cy.get('[data-test="dhis2-uicore-layer"]').click()
17
+ })
18
+
19
+ When('the user clicks on the button coordinates', () => {
20
+ cy.getPositionsBySelectors('button').then(([rect]) => {
21
+ // Get button center coordinates
22
+ const buttonCenterX = rect.left + rect.width / 2
23
+ const buttonCenterY = rect.top + rect.height / 2
24
+
25
+ // click body on the button center
26
+ cy.get('body').click(buttonCenterX, buttonCenterY)
27
+ })
28
+ })
29
+
30
+ Then('the onClick handler of the button is called', () => {
31
+ cy.window().should((win) => {
32
+ expect(win.onButtonClick).to.be.calledOnce
33
+ })
34
+ })
35
+
36
+ Then('the onClick handler of the layer is called', () => {
37
+ cy.window().should((win) => {
38
+ expect(win.onLayerClick).to.be.calledOnce
39
+ })
40
+ })
41
+
42
+ Then('the onClick handler of the button is not called', () => {
43
+ cy.window().should((win) => {
44
+ expect(win.onButtonClick).to.have.callCount(0)
45
+ })
46
+ })
47
+
48
+ Then('the onClick handler of the layer is not called', () => {
49
+ cy.window().should((win) => {
50
+ expect(win.onLayerClick).to.have.callCount(0)
51
+ })
52
+ })
@@ -0,0 +1,17 @@
1
+ Feature: The Layer has configurable click behaviour
2
+
3
+ Scenario: A blocking layer
4
+ Given a Layer with a button below it is rendered
5
+ When the user clicks on the button coordinates
6
+ Then the onClick handler of the button is not called
7
+
8
+ Scenario: A blocking layer with an onClick handler
9
+ Given a Layer with a button in it is rendered
10
+ When the user clicks on the layer
11
+ Then the onClick handler of the layer is called
12
+
13
+ Scenario: Clicks orginating from children are ignored
14
+ Given a Layer with a button in it is rendered
15
+ When the user clicks the button
16
+ Then the onClick handler of the button is called
17
+ But the onClick handler of the layer is not called
@@ -0,0 +1,79 @@
1
+ import { Given, Then } from '@badeball/cypress-cucumber-preprocessor'
2
+
3
+ Given('two equal sibling Layers are rendered', () => {
4
+ cy.visitStory('Layer', 'Equal Siblings')
5
+ })
6
+
7
+ Given(
8
+ 'an alert, blocking, and applicatioTop Layer are rendered as siblings',
9
+ () => {
10
+ cy.visitStory('Layer', 'Inequal Siblings')
11
+ }
12
+ )
13
+
14
+ Given('a blocking layer is rendered as the child of an alert layer', () => {
15
+ cy.visitStory('Layer', 'Nested Lower Levels')
16
+ })
17
+
18
+ Given('an alert layer is rendered as the child of a blocking layer', () => {
19
+ cy.visitStory('Layer', 'Nested Higher Levels')
20
+ })
21
+
22
+ Given(
23
+ 'a layer with level 1001 is a sibling of 3 nested layers with level 1000',
24
+ () => {
25
+ cy.visitStory('Layer', 'Levels Are Respected When Nesting')
26
+ }
27
+ )
28
+
29
+ Given(
30
+ 'an applicatioTop layer with a nested alert layer with a blocking sibling',
31
+ () => {
32
+ cy.visitStory('Layer', 'Nested Higher Level Ends On Top')
33
+ }
34
+ )
35
+
36
+ Then('the second layer is on top of the first layer', () => {
37
+ // Wouldn't work if the element wasn't ontop of the second layer.
38
+ // Cypress would fail, stating:
39
+ // "[...] is being covered by another element: [...]"
40
+ cy.get('.backdrop:visible').click()
41
+ cy.window().should((win) => {
42
+ expect(win.onLayerClick).to.be.calledOnce
43
+ expect(win.onLayerClick).to.be.calledWith('second')
44
+ })
45
+ })
46
+
47
+ Then('the alert layer is on top', () => {
48
+ cy.get('.backdrop:visible').click()
49
+ cy.window().should((win) => {
50
+ expect(win.onLayerClick).to.be.calledOnce
51
+ expect(win.onLayerClick).to.be.calledWith('alert')
52
+ })
53
+ })
54
+
55
+ Then('the layer with level 1001 is on top', () => {
56
+ cy.get('.backdrop:visible').click()
57
+ cy.window().should((win) => {
58
+ expect(win.onLayerClick).to.be.calledOnce
59
+ expect(win.onLayerClick).to.be.calledWith('1001')
60
+ })
61
+ })
62
+
63
+ Then('the blocking layer is on top', () => {
64
+ cy.get('.backdrop:visible').click()
65
+ cy.window().should((win) => {
66
+ expect(win.onLayerClick).to.be.calledOnce
67
+ expect(win.onLayerClick).to.be.calledWith('blocking')
68
+ })
69
+ })
70
+
71
+ Then('the blocking layer is a child of the alert layer', () => {
72
+ cy.get('[data-test="blocking"]')
73
+ .parent()
74
+ .should('have.data', 'test', 'alert')
75
+ })
76
+
77
+ Then('the alert layer is a sibling of the blocking layer', () => {
78
+ cy.get('[data-test="blocking"]').next().should('have.data', 'test', 'alert')
79
+ })
@@ -0,0 +1,34 @@
1
+ Feature: Layers are stacked on top of each other
2
+
3
+ Scenario: Equal sibling layers
4
+ Given two equal sibling Layers are rendered
5
+ Then the second layer is on top of the first layer
6
+
7
+ Scenario: Inequal sibling layers
8
+ Given an alert, blocking, and applicatioTop Layer are rendered as siblings
9
+ Then the alert layer is on top
10
+
11
+ # use zIndex stacking context
12
+ Scenario: Nesting Layer elements with lower levels
13
+ Given a blocking layer is rendered as the child of an alert layer
14
+ Then the blocking layer is on top
15
+ And the blocking layer is a child of the alert layer
16
+
17
+ # avoid stacking context upper bound issue
18
+ Scenario: Appending nested Layers with higher levels to the body
19
+ Given an alert layer is rendered as the child of a blocking layer
20
+ Then the alert layer is on top
21
+ And the alert layer is a sibling of the blocking layer
22
+
23
+ # verify that bug from previous implementation is not there
24
+ # that bug was as follows:
25
+ # nested layers top element zIndex = 1000 + 1 + 1 = 1002
26
+ # sibling layer element zIndex = 1001
27
+ # so layer level 1001 would be below the nested layer with level 1000
28
+ Scenario: Levels are respected when nesting layers
29
+ Given a layer with level 1001 is a sibling of 3 nested layers with level 1000
30
+ Then the layer with level 1001 is on top
31
+
32
+ Scenario: Nested higher levels still end up on top
33
+ Given an applicatioTop layer with a nested alert layer with a blocking sibling
34
+ Then the alert layer is on top
package/src/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { Layer } from './layer.js'
2
+
3
+ // DEPRECATED: Remove in 7.0.0
4
+ export { useLayerContext } from './use-layer-context.js'
@@ -0,0 +1,7 @@
1
+ import { createContext } from 'react'
2
+
3
+ export const LayerContext = createContext({
4
+ node: document.body,
5
+ level: 0,
6
+ })
7
+ export const { Provider, Consumer } = LayerContext
@@ -0,0 +1,113 @@
1
+ import { layers } from '@dhis2/ui-constants'
2
+ import React from 'react'
3
+ import { Layer } from './layer.js'
4
+
5
+ window.onButtonClick = window.Cypress && window.Cypress.cy.stub()
6
+ window.onLayerClick = window.Cypress && window.Cypress.cy.stub()
7
+
8
+ const createNamedLayerClick = (name) => () => {
9
+ window.onLayerClick(name)
10
+ }
11
+
12
+ export default { title: 'Layer', component: Layer }
13
+
14
+ export const Default = () => (
15
+ <Layer>
16
+ <p>I am a child</p>
17
+ </Layer>
18
+ )
19
+
20
+ export const Blocking = () => (
21
+ <>
22
+ <button onClick={window.onButtonClick}>Test</button>
23
+ <Layer />
24
+ </>
25
+ )
26
+
27
+ export const WithClickHandler = () => (
28
+ <Layer onBackdropClick={window.onLayerClick}>
29
+ <button onClick={window.onButtonClick}>Test</button>
30
+ </Layer>
31
+ )
32
+
33
+ export const EqualSiblings = () => (
34
+ <>
35
+ <Layer onBackdropClick={createNamedLayerClick('first')} />
36
+ <Layer onBackdropClick={createNamedLayerClick('second')} />
37
+ </>
38
+ )
39
+
40
+ export const InequalSiblings = () => (
41
+ <>
42
+ <Layer
43
+ level={layers.alert}
44
+ onBackdropClick={createNamedLayerClick('alert')}
45
+ />
46
+ <Layer
47
+ level={layers.blocking}
48
+ onBackdropClick={createNamedLayerClick('blocking')}
49
+ />
50
+ <Layer
51
+ level={layers.applicationTop}
52
+ onBackdropClick={createNamedLayerClick('applicationTop')}
53
+ />
54
+ </>
55
+ )
56
+
57
+ export const NestedLowerLevels = () => (
58
+ <Layer
59
+ level={layers.alert}
60
+ dataTest="alert"
61
+ onBackdropClick={createNamedLayerClick('alert')}
62
+ >
63
+ <Layer
64
+ level={layers.blocking}
65
+ dataTest="blocking"
66
+ disablePortal={true}
67
+ onBackdropClick={createNamedLayerClick('blocking')}
68
+ />
69
+ </Layer>
70
+ )
71
+
72
+ export const NestedHigherLevels = () => (
73
+ <Layer
74
+ level={layers.blocking}
75
+ dataTest="blocking"
76
+ onBackdropClick={createNamedLayerClick('blocking')}
77
+ >
78
+ <Layer
79
+ level={layers.alert}
80
+ dataTest="alert"
81
+ onBackdropClick={createNamedLayerClick('alert')}
82
+ />
83
+ </Layer>
84
+ )
85
+
86
+ export const LevelsAreRespectedWhenNesting = () => (
87
+ <>
88
+ <Layer level={1000}>
89
+ <Layer level={1000}>
90
+ <Layer
91
+ level={1000}
92
+ onBackdropClick={createNamedLayerClick('1000')}
93
+ />
94
+ </Layer>
95
+ </Layer>
96
+ <Layer level={1001} onBackdropClick={createNamedLayerClick('1001')} />
97
+ </>
98
+ )
99
+
100
+ export const NestedHigherLevelEndsOnTop = () => (
101
+ <>
102
+ <Layer level={layers.applicationTop}>
103
+ <Layer
104
+ level={layers.alert}
105
+ onBackdropClick={createNamedLayerClick('alert')}
106
+ />
107
+ </Layer>
108
+ <Layer
109
+ level={layers.blocking}
110
+ onBackdropClick={createNamedLayerClick('blocking')}
111
+ />
112
+ </>
113
+ )
package/src/layer.js ADDED
@@ -0,0 +1,90 @@
1
+ import { deprecated } from '@dhis2/prop-types'
2
+ import { Portal } from '@dhis2-ui/portal'
3
+ import cx from 'classnames'
4
+ import PropTypes from 'prop-types'
5
+ import React from 'react'
6
+
7
+ const Layer = ({
8
+ children,
9
+ className,
10
+ dataTest = 'dhis2-uicore-layer',
11
+ disablePortal,
12
+ level = 'auto',
13
+ onBackdropClick,
14
+ onClick,
15
+ position = 'fixed',
16
+ translucent,
17
+ }) => {
18
+ const resolvedOnClick = onBackdropClick || onClick
19
+
20
+ return (
21
+ <Portal disable={disablePortal}>
22
+ <div
23
+ className={cx('layer', className, position, {
24
+ translucent,
25
+ })}
26
+ data-test={dataTest}
27
+ >
28
+ {resolvedOnClick && (
29
+ <div
30
+ className="backdrop"
31
+ onClick={(event) => resolvedOnClick({}, event)}
32
+ />
33
+ )}
34
+ {children}
35
+
36
+ <style jsx>{`
37
+ div {
38
+ z-index: ${level};
39
+ }
40
+ `}</style>
41
+
42
+ <style jsx>{`
43
+ div {
44
+ inset-block-start: 0;
45
+ inset-inline-start: 0;
46
+ min-height: 100vh;
47
+ min-width: 100vw;
48
+ }
49
+ div.fixed {
50
+ position: fixed;
51
+ height: 100vh;
52
+ width: 100vw;
53
+ }
54
+ div.absolute {
55
+ position: absolute;
56
+ height: 100%;
57
+ width: 100%;
58
+ }
59
+ div.translucent {
60
+ background-color: rgba(33, 43, 54, 0.4);
61
+ }
62
+ div.backdrop {
63
+ position: absolute;
64
+ inset: 0;
65
+ z-index: -1;
66
+ }
67
+ `}</style>
68
+ </div>
69
+ </Portal>
70
+ )
71
+ }
72
+
73
+ Layer.propTypes = {
74
+ children: PropTypes.node,
75
+ className: PropTypes.string,
76
+ dataTest: PropTypes.string,
77
+ /** Disable the Portal, useful for nesting layers */
78
+ disablePortal: PropTypes.bool,
79
+ /** Z-index level */
80
+ level: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
81
+ position: PropTypes.oneOf(['absolute', 'fixed']),
82
+ /** Adds a semi-transparent background */
83
+ translucent: PropTypes.bool,
84
+ /** Backdrop click handler */
85
+ onBackdropClick: PropTypes.func,
86
+ /** Click handler - DEPRECATED */
87
+ onClick: deprecated(PropTypes.func, 'Please use "onBackdropClick" instead'),
88
+ }
89
+
90
+ export { Layer }
@@ -0,0 +1,69 @@
1
+ import { Center } from '@dhis2-ui/center'
2
+ import { CircularLoader } from '@dhis2-ui/loader'
3
+ import React from 'react'
4
+ import { Layer } from './layer.js'
5
+
6
+ const description = `
7
+ Layers are used for creating different levels of stacking of interface elements.
8
+ See more about stacking guidelines at the [design system](https://github.com/dhis2/design-system/blob/master/principles/layout.md#stacking).
9
+
10
+ Layers are used in Modals, Popovers, and Alerts.
11
+
12
+ \`\`\`js
13
+ import { Layer } from '@dhis2/ui'
14
+ \`\`\`
15
+
16
+ _**Note:** These demos may take some time to load._
17
+ `
18
+
19
+ export default {
20
+ title: 'Layer',
21
+ component: Layer,
22
+ /**
23
+ * `inlineStories: false` renders these layers in iframes instead of inline.
24
+ * This fixes an issue where all the layers on the docs page render on top
25
+ * of eachother, each covering the whole screen.
26
+ * There is a performance tradeof, and they are slow to load.
27
+ */
28
+ parameters: {
29
+ docs: {
30
+ inlineStories: false,
31
+ iframeHeight: '180px',
32
+ description: { component: description },
33
+ },
34
+ },
35
+ // Handle weird treatment of non-literal defaultProps (see Transfer.stories)
36
+ args: {
37
+ position: 'fixed',
38
+ dataTest: 'dhis2-uicore-layer',
39
+ level: 'auto',
40
+ },
41
+ }
42
+
43
+ const Template = (args) => (
44
+ <>
45
+ <Layer {...args} />
46
+
47
+ <h1>Text behind the layer</h1>
48
+ <p>Lorem ipsum</p>
49
+ </>
50
+ )
51
+
52
+ export const Default = Template.bind({})
53
+
54
+ export const Translucent = Template.bind({})
55
+ Translucent.args = { translucent: true }
56
+
57
+ export const WithOnBackdropClick = Template.bind({})
58
+ WithOnBackdropClick.args = {
59
+ onBackdropClick: () => alert('layer backdrop was clicked'),
60
+ }
61
+
62
+ export const WithCenteredContentCircularLoader = Template.bind({})
63
+ WithCenteredContentCircularLoader.args = {
64
+ children: (
65
+ <Center>
66
+ <CircularLoader />
67
+ </Center>
68
+ ),
69
+ }
@@ -0,0 +1,4 @@
1
+ import { useContext } from 'react'
2
+ import { LayerContext } from './layer-context.js'
3
+
4
+ export const useLayerContext = () => useContext(LayerContext)