@1024pix/pix-ui 17.1.0 → 18.0.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 +18 -0
- package/addon/components/pix-modal.hbs +2 -2
- package/addon/components/pix-sidebar.hbs +34 -0
- package/addon/components/pix-sidebar.js +24 -0
- package/addon/styles/_pix-banner.scss +9 -9
- package/addon/styles/_pix-icon-button.scss +19 -26
- package/addon/styles/_pix-modal.scss +6 -0
- package/addon/styles/_pix-sidebar.scss +80 -0
- package/addon/styles/addon.scss +1 -0
- package/app/components/pix-sidebar.js +1 -0
- package/app/modifiers/trap-focus.js +84 -34
- package/app/stories/pix-icon-button.stories.js +14 -3
- package/app/stories/pix-icon-button.stories.mdx +28 -20
- package/app/stories/pix-modal.stories.js +29 -19
- package/app/stories/pix-modal.stories.mdx +1 -1
- package/app/stories/pix-sidebar.stories.js +61 -0
- package/app/stories/pix-sidebar.stories.mdx +57 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Pix-UI Changelog
|
|
2
2
|
|
|
3
|
+
## v18.0.0 (01/09/2022)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### :bug: Correction
|
|
7
|
+
- [#246](https://github.com/1024pix/pix-ui/pull/246) [BUGFIX] Mettre le focus sur le premier élement où est utiliser le `trap-focus`.
|
|
8
|
+
|
|
9
|
+
## v17.2.1 (31/08/2022)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### :rocket: Amélioration
|
|
13
|
+
- [#247](https://github.com/1024pix/pix-ui/pull/247) [FEATURE] Amélioration couleurs `PixIconButton`
|
|
14
|
+
|
|
15
|
+
## v17.2.0 (24/08/2022)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### :rocket: Amélioration
|
|
19
|
+
- [#245](https://github.com/1024pix/pix-ui/pull/245) [FEATURE] Ajouter un composant Sidebar (PIX-4298)
|
|
20
|
+
|
|
3
21
|
## v17.1.0 (22/08/2022)
|
|
4
22
|
|
|
5
23
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<div
|
|
2
|
+
class="pix-sidebar__overlay {{unless @showSidebar ' pix-sidebar__overlay--hidden'}}"
|
|
3
|
+
{{on "click" this.closeAction}}
|
|
4
|
+
{{trap-focus @showSidebar}}
|
|
5
|
+
{{on-escape-action this.closeAction}}
|
|
6
|
+
>
|
|
7
|
+
<div
|
|
8
|
+
class="pix-sidebar {{unless @showSidebar ' pix-sidebar--hidden'}}"
|
|
9
|
+
role="dialog"
|
|
10
|
+
aria-labelledby="sidebar-title"
|
|
11
|
+
aria-describedby="sidebar-content"
|
|
12
|
+
aria-modal="true"
|
|
13
|
+
{{on "click" this.stopPropagation}}
|
|
14
|
+
...attributes
|
|
15
|
+
>
|
|
16
|
+
<header class="pix-sidebar__header">
|
|
17
|
+
<h1 id="sidebar-title" class="pix-sidebar__title">{{@title}}</h1>
|
|
18
|
+
<PixIconButton
|
|
19
|
+
@icon="xmark"
|
|
20
|
+
@triggerAction={{this.closeAction}}
|
|
21
|
+
@ariaLabel="Fermer"
|
|
22
|
+
@size="small"
|
|
23
|
+
@withBackground={{true}}
|
|
24
|
+
class="pix-sidebar__close-button"
|
|
25
|
+
/>
|
|
26
|
+
</header>
|
|
27
|
+
<main id="sidebar-content" class="pix-sidebar__content">
|
|
28
|
+
{{yield to="content"}}
|
|
29
|
+
</main>
|
|
30
|
+
<footer class="pix-sidebar__footer">
|
|
31
|
+
{{yield to="footer"}}
|
|
32
|
+
</footer>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Component from '@glimmer/component';
|
|
2
|
+
import { action } from '@ember/object';
|
|
3
|
+
|
|
4
|
+
export default class PixSidebar extends Component {
|
|
5
|
+
constructor(...args) {
|
|
6
|
+
super(...args);
|
|
7
|
+
|
|
8
|
+
if (!this.args.title) {
|
|
9
|
+
throw new Error('ERROR in PixSidebar component: @title argument is required.');
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@action
|
|
14
|
+
stopPropagation(event) {
|
|
15
|
+
event.stopPropagation();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@action
|
|
19
|
+
closeAction(params) {
|
|
20
|
+
if (this.args.onClose) {
|
|
21
|
+
this.args.onClose(params);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
.pix-icon-button {
|
|
33
33
|
background-color: $pix-primary-5;
|
|
34
34
|
color: $pix-primary-70;
|
|
35
|
-
&:hover,
|
|
36
|
-
&:focus,
|
|
37
|
-
&:active {
|
|
35
|
+
&:hover:enabled,
|
|
36
|
+
&:focus:enabled,
|
|
37
|
+
&:active:enabled {
|
|
38
38
|
background-color: $pix-primary-10;
|
|
39
39
|
color: $pix-primary-70;
|
|
40
40
|
}
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
.pix-icon-button {
|
|
48
48
|
background-color: $pix-warning-5;
|
|
49
49
|
color: $pix-warning-70;
|
|
50
|
-
&:hover,
|
|
51
|
-
&:focus,
|
|
52
|
-
&:active {
|
|
50
|
+
&:hover:enabled,
|
|
51
|
+
&:focus:enabled,
|
|
52
|
+
&:active:enabled {
|
|
53
53
|
background-color: $pix-warning-10;
|
|
54
54
|
color: $pix-warning-70;
|
|
55
55
|
}
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
.pix-icon-button {
|
|
63
63
|
background-color: $pix-error-5;
|
|
64
64
|
color: $pix-error-70;
|
|
65
|
-
&:hover,
|
|
66
|
-
&:focus,
|
|
67
|
-
&:active {
|
|
65
|
+
&:hover:enabled,
|
|
66
|
+
&:focus:enabled,
|
|
67
|
+
&:active:enabled {
|
|
68
68
|
background-color: $pix-error-10;
|
|
69
69
|
color: $pix-error-70;
|
|
70
70
|
}
|
|
@@ -10,35 +10,36 @@
|
|
|
10
10
|
align-items: center;
|
|
11
11
|
justify-content: center;
|
|
12
12
|
background-color: transparent;
|
|
13
|
+
color: $pix-neutral-60;
|
|
13
14
|
|
|
14
15
|
&:disabled {
|
|
15
16
|
opacity: 0.5;
|
|
16
|
-
cursor:
|
|
17
|
+
cursor: not-allowed;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
&:hover
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
background-color: transparent;
|
|
25
|
-
}
|
|
20
|
+
&:hover:enabled {
|
|
21
|
+
background-color: $pix-neutral-20;
|
|
22
|
+
box-shadow: none;
|
|
23
|
+
border: 0;
|
|
24
|
+
color: $pix-neutral-60;
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
&:
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
&:focus:enabled {
|
|
28
|
+
background-color: $pix-neutral-60;
|
|
29
|
+
box-shadow: 0 0 0 2px $pix-neutral-60;
|
|
30
|
+
border: 2px solid $pix-neutral-0;
|
|
31
|
+
color: $pix-neutral-0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&:active:enabled {
|
|
35
|
+
background-color: $pix-neutral-22;
|
|
36
|
+
box-shadow: none;
|
|
37
|
+
border: 0;
|
|
38
|
+
color: $pix-neutral-60;
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
&--background {
|
|
34
42
|
background-color: $pix-neutral-10;
|
|
35
|
-
&:hover,
|
|
36
|
-
&:focus,
|
|
37
|
-
&:active {
|
|
38
|
-
&:disabled {
|
|
39
|
-
background-color: $pix-neutral-15;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
&--small {
|
|
@@ -52,12 +53,4 @@
|
|
|
52
53
|
height: 38px;
|
|
53
54
|
font-size: 1.25rem;
|
|
54
55
|
}
|
|
55
|
-
|
|
56
|
-
&--dark-grey {
|
|
57
|
-
color: $pix-neutral-60;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
&--light-grey {
|
|
61
|
-
color: $pix-neutral-60;
|
|
62
|
-
}
|
|
63
56
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
.pix-sidebar__overlay {
|
|
2
|
+
position: fixed;
|
|
3
|
+
z-index: 1000;
|
|
4
|
+
top: 0;
|
|
5
|
+
bottom: 0;
|
|
6
|
+
left: 0;
|
|
7
|
+
right: 0;
|
|
8
|
+
overflow-y: scroll;
|
|
9
|
+
background-color: rgba(52, 69, 99, 0.7);
|
|
10
|
+
transition: all .3s ease-in-out;
|
|
11
|
+
|
|
12
|
+
&--hidden {
|
|
13
|
+
visibility: hidden;
|
|
14
|
+
opacity: 0;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
$sidebar-padding: 16px;
|
|
19
|
+
$mobile-close-button-size: 32px;
|
|
20
|
+
$tablet-close-button-size: 40px;
|
|
21
|
+
$space-between-title-and-close-button: 8px;
|
|
22
|
+
$button-margin: 16px;
|
|
23
|
+
|
|
24
|
+
.pix-sidebar {
|
|
25
|
+
@import 'reset-css';
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: column;
|
|
28
|
+
height: 100%;
|
|
29
|
+
max-width: 320px;
|
|
30
|
+
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
|
|
31
|
+
background: $pix-neutral-0;
|
|
32
|
+
transform: translate(0);
|
|
33
|
+
transition: transform .3s ease-in-out;
|
|
34
|
+
|
|
35
|
+
&--hidden {
|
|
36
|
+
transform: translate(-100%);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&__header {
|
|
40
|
+
border-bottom: 1px solid $pix-neutral-20;
|
|
41
|
+
padding: $sidebar-padding;
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
justify-content: space-between;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
&__close-button {
|
|
48
|
+
flex-shrink: 0;
|
|
49
|
+
|
|
50
|
+
@include device-is('tablet') {
|
|
51
|
+
height: $tablet-close-button-size;
|
|
52
|
+
width: $tablet-close-button-size;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
&__content {
|
|
57
|
+
flex-grow: 1;
|
|
58
|
+
overflow: auto;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&__title {
|
|
62
|
+
@include h4;
|
|
63
|
+
margin-bottom: 0;
|
|
64
|
+
color: $pix-neutral-90;
|
|
65
|
+
padding-right: $mobile-close-button-size + $space-between-title-and-close-button;
|
|
66
|
+
|
|
67
|
+
@include device-is('tablet') {
|
|
68
|
+
padding-right: $tablet-close-button-size + $space-between-title-and-close-button;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
&__content {
|
|
73
|
+
padding: $sidebar-padding 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
&__footer {
|
|
77
|
+
padding: $sidebar-padding;
|
|
78
|
+
border-top: 1px solid $pix-neutral-20;
|
|
79
|
+
}
|
|
80
|
+
}
|
package/addon/styles/addon.scss
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@1024pix/pix-ui/components/pix-sidebar';
|
|
@@ -1,45 +1,25 @@
|
|
|
1
1
|
import { modifier } from 'ember-modifier';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
const [firstFocusableElement] = findFocusableElements(element);
|
|
5
|
-
|
|
6
|
-
firstFocusableElement.focus();
|
|
7
|
-
|
|
8
|
-
function handleKeyDown(event) {
|
|
9
|
-
const TAB_KEY = 9;
|
|
10
|
-
const focusableElements = findFocusableElements(element);
|
|
11
|
-
const [firstFocusableElement] = focusableElements;
|
|
12
|
-
const lastFocusableElement = focusableElements[focusableElements.length - 1];
|
|
13
|
-
|
|
14
|
-
if (event.keyCode !== TAB_KEY) {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
3
|
+
let sourceActiveElement = null;
|
|
17
4
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
event.preventDefault();
|
|
21
|
-
lastFocusableElement.focus();
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function handleForwardTab() {
|
|
26
|
-
if (document.activeElement === lastFocusableElement) {
|
|
27
|
-
event.preventDefault();
|
|
28
|
-
firstFocusableElement.focus();
|
|
29
|
-
}
|
|
30
|
-
}
|
|
5
|
+
export default modifier(function trapFocus(element, [isOpen]) {
|
|
6
|
+
const [firstFocusableElement] = findFocusableElements(element);
|
|
31
7
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
8
|
+
if (isOpen) {
|
|
9
|
+
sourceActiveElement = document.activeElement;
|
|
10
|
+
focusElement(firstFocusableElement, element);
|
|
11
|
+
} else {
|
|
12
|
+
focusElement(sourceActiveElement, element);
|
|
37
13
|
}
|
|
38
14
|
|
|
39
|
-
element.addEventListener('keydown',
|
|
15
|
+
element.addEventListener('keydown', (event) => {
|
|
16
|
+
handleKeyDown(event, element);
|
|
17
|
+
});
|
|
40
18
|
|
|
41
19
|
return () => {
|
|
42
|
-
element.removeEventListener('keydown',
|
|
20
|
+
element.removeEventListener('keydown', (event) => {
|
|
21
|
+
handleKeyDown(event, element);
|
|
22
|
+
});
|
|
43
23
|
};
|
|
44
24
|
});
|
|
45
25
|
|
|
@@ -50,3 +30,73 @@ function findFocusableElements(element) {
|
|
|
50
30
|
),
|
|
51
31
|
].filter((el) => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden'));
|
|
52
32
|
}
|
|
33
|
+
|
|
34
|
+
function focusElement(elementToFocus, element) {
|
|
35
|
+
let focusOnce = false;
|
|
36
|
+
|
|
37
|
+
const handleTransitionEnd = () => {
|
|
38
|
+
if (!focusOnce) {
|
|
39
|
+
elementToFocus.focus();
|
|
40
|
+
focusOnce = true;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (hasTransitionDuration(element)) {
|
|
45
|
+
element.addEventListener('transitionend', handleTransitionEnd);
|
|
46
|
+
} else if (hasAnimationDuration(element)) {
|
|
47
|
+
element.addEventListener('animationend', handleTransitionEnd);
|
|
48
|
+
} else {
|
|
49
|
+
elementToFocus.focus();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return () => {
|
|
53
|
+
if (hasTransitionDuration(element)) {
|
|
54
|
+
element.removeEventListener('transitionend', handleTransitionEnd);
|
|
55
|
+
} else if (hasAnimationDuration(element)) {
|
|
56
|
+
element.removeEventListener('animationend', handleTransitionEnd);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function hasTransitionDuration(element) {
|
|
62
|
+
return hasDurationByKey(element, 'transition-duration');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function hasAnimationDuration(element) {
|
|
66
|
+
return hasDurationByKey(element, 'animation-duration');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function hasDurationByKey(element, key) {
|
|
70
|
+
return !['', '0s'].includes(getComputedStyle(element, null).getPropertyValue(key));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function handleKeyDown(event, element) {
|
|
74
|
+
const TAB_KEY = 9;
|
|
75
|
+
const focusableElements = findFocusableElements(element);
|
|
76
|
+
const [firstFocusableElement] = focusableElements;
|
|
77
|
+
const lastFocusableElement = focusableElements[focusableElements.length - 1];
|
|
78
|
+
|
|
79
|
+
if (event.keyCode !== TAB_KEY) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const handleBackwardTab = () => {
|
|
84
|
+
if (document.activeElement === firstFocusableElement) {
|
|
85
|
+
event.preventDefault();
|
|
86
|
+
lastFocusableElement.focus();
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const handleForwardTab = () => {
|
|
91
|
+
if (document.activeElement === lastFocusableElement) {
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
firstFocusableElement.focus();
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (event.shiftKey) {
|
|
98
|
+
handleBackwardTab();
|
|
99
|
+
} else {
|
|
100
|
+
handleForwardTab();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -11,31 +11,42 @@ const Template = (args) => {
|
|
|
11
11
|
@triggerAction={{triggerAction}}
|
|
12
12
|
@withBackground={{withBackground}}
|
|
13
13
|
@size={{size}}
|
|
14
|
+
disabled={{disabled}}
|
|
15
|
+
aria-disabled={{disabled}}
|
|
14
16
|
/>
|
|
15
17
|
`,
|
|
16
18
|
context: args,
|
|
17
19
|
};
|
|
18
20
|
};
|
|
19
21
|
|
|
22
|
+
const triggerAction = () => Promise.resolve();
|
|
23
|
+
|
|
20
24
|
export const Default = Template.bind({});
|
|
21
25
|
Default.args = {
|
|
22
26
|
ariaLabel: 'Action du bouton',
|
|
23
27
|
icon: 'xmark',
|
|
24
|
-
triggerAction
|
|
25
|
-
return new Promise().resolves();
|
|
26
|
-
},
|
|
28
|
+
triggerAction,
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
export const small = Template.bind({});
|
|
30
32
|
small.args = {
|
|
31
33
|
...Default.args,
|
|
32
34
|
size: 'small',
|
|
35
|
+
triggerAction,
|
|
33
36
|
};
|
|
34
37
|
|
|
35
38
|
export const withBackground = Template.bind({});
|
|
36
39
|
withBackground.args = {
|
|
37
40
|
...Default.args,
|
|
38
41
|
withBackground: true,
|
|
42
|
+
triggerAction,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const disabled = Template.bind({});
|
|
46
|
+
disabled.args = {
|
|
47
|
+
...Default.args,
|
|
48
|
+
disabled: true,
|
|
49
|
+
triggerAction,
|
|
39
50
|
};
|
|
40
51
|
|
|
41
52
|
export const argTypes = {
|
|
@@ -3,8 +3,8 @@ import centered from '@storybook/addon-centered/ember';
|
|
|
3
3
|
import * as stories from './pix-icon-button.stories.js';
|
|
4
4
|
|
|
5
5
|
<Meta
|
|
6
|
-
title=
|
|
7
|
-
component=
|
|
6
|
+
title="Basics/Icon button"
|
|
7
|
+
component="PixIconButton"
|
|
8
8
|
decorators={[centered]}
|
|
9
9
|
argTypes={stories.argTypes}
|
|
10
10
|
/>
|
|
@@ -37,6 +37,14 @@ Le bouton avec le fond grisé.
|
|
|
37
37
|
<Story name="With Background" story={stories.withBackground} height={60} />
|
|
38
38
|
</Canvas>
|
|
39
39
|
|
|
40
|
+
## Disabled
|
|
41
|
+
|
|
42
|
+
Exemple avec le bouton disabled
|
|
43
|
+
|
|
44
|
+
<Canvas>
|
|
45
|
+
<Story name="Disabled" story={stories.disabled} height={60} />
|
|
46
|
+
</Canvas>
|
|
47
|
+
|
|
40
48
|
## Accessibilité / aria-label
|
|
41
49
|
|
|
42
50
|
L'attribut `aria-label` étant indispensable pour l'accessibilité de ce composant, la propriété `ariaLabel` a été ajouté.
|
|
@@ -48,42 +56,42 @@ L'attribut `aria-label` étant indispensable pour l'accessibilité de ce composa
|
|
|
48
56
|
## Usage
|
|
49
57
|
|
|
50
58
|
Par défaut
|
|
59
|
+
|
|
51
60
|
```html
|
|
52
|
-
<PixIconButton
|
|
53
|
-
@ariaLabel="L'action du bouton"
|
|
54
|
-
@icon="icon"
|
|
55
|
-
@triggerAction={{triggerAction}}
|
|
56
|
-
/>
|
|
61
|
+
<PixIconButton @ariaLabel="L'action du bouton" @icon="icon" @triggerAction="{{triggerAction}}" />
|
|
57
62
|
```
|
|
58
63
|
|
|
59
64
|
Bouton de fermeture
|
|
65
|
+
|
|
60
66
|
```html
|
|
61
67
|
<PixIconButton
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
@ariaLabel="L'action du bouton"
|
|
69
|
+
@icon="times"
|
|
70
|
+
@triggerAction="{{triggerAction}}"
|
|
71
|
+
@withBackground="{{true}}"
|
|
66
72
|
/>
|
|
67
73
|
```
|
|
68
74
|
|
|
69
75
|
Bouton d'action
|
|
76
|
+
|
|
70
77
|
```html
|
|
71
78
|
<PixIconButton
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
@ariaLabel="L'action du bouton"
|
|
80
|
+
@icon="arrow-left"
|
|
81
|
+
@triggerAction="{{triggerAction}}"
|
|
82
|
+
@size="small"
|
|
83
|
+
@withBackground="{{true}}"
|
|
77
84
|
/>
|
|
78
85
|
```
|
|
79
86
|
|
|
80
87
|
État disabled
|
|
88
|
+
|
|
81
89
|
```html
|
|
82
90
|
<PixIconButton
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
@ariaLabel="L'action du bouton"
|
|
92
|
+
@icon="xmark"
|
|
93
|
+
disabled="{{true}}"
|
|
94
|
+
aria-disabled="{{true}}"
|
|
87
95
|
/>
|
|
88
96
|
```
|
|
89
97
|
|
|
@@ -3,22 +3,23 @@ import { hbs } from 'ember-cli-htmlbars';
|
|
|
3
3
|
export const Template = (args) => {
|
|
4
4
|
return {
|
|
5
5
|
template: hbs`
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
6
|
+
<PixModal @showModal={{showModal}} @title={{this.title}} @onCloseButtonClick={{fn (mut showModal) (not showModal)}} >
|
|
7
|
+
<:content>
|
|
8
|
+
<p>
|
|
9
|
+
Une fenêtre modale est, dans une interface graphique, une fenêtre qui prend le contrôle total du clavier et
|
|
10
|
+
de l'écran. Elle est en général associée à une question à laquelle il est impératif que l'utilisateur
|
|
11
|
+
réponde avant de poursuivre, ou de modifier quoi que ce soit. La fenêtre modale a pour propos : d'obtenir
|
|
12
|
+
des informations de l'utilisateur (ces informations sont nécessaires pour réaliser une opération) ; de
|
|
13
|
+
fournir une information à l'utilisateur (ce dernier doit en prendre connaissance avant de pouvoir continuer
|
|
14
|
+
à utiliser l'application).
|
|
15
|
+
</p>
|
|
16
|
+
</:content>
|
|
17
|
+
<:footer>
|
|
18
|
+
<PixButton @backgroundColor="transparent-light" @isBorderVisible="true" @triggerAction={{fn (mut showModal) (not showModal)}}>Annuler</PixButton>
|
|
19
|
+
<PixButton @triggerAction={{fn (mut showModal) (not showModal)}}>Valider</PixButton>
|
|
20
|
+
</:footer>
|
|
21
|
+
</PixModal>
|
|
22
|
+
<PixButton @triggerAction={{fn (mut showModal) (not showModal)}}>Ouvrir la modale</PixButton>
|
|
22
23
|
`,
|
|
23
24
|
context: args,
|
|
24
25
|
};
|
|
@@ -26,10 +27,9 @@ export const Template = (args) => {
|
|
|
26
27
|
|
|
27
28
|
export const Default = Template.bind({});
|
|
28
29
|
Default.args = {
|
|
30
|
+
showModal: true,
|
|
29
31
|
title: "Qu'est-ce qu'une modale ?",
|
|
30
|
-
onCloseButtonClick: () => {
|
|
31
|
-
alert('Action : fermer modale');
|
|
32
|
-
},
|
|
32
|
+
onCloseButtonClick: () => {},
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
export const argTypes = {
|
|
@@ -45,4 +45,14 @@ export const argTypes = {
|
|
|
45
45
|
type: { name: 'function', required: true },
|
|
46
46
|
defaultValue: null,
|
|
47
47
|
},
|
|
48
|
+
showModal: {
|
|
49
|
+
name: 'showModal',
|
|
50
|
+
description: "Gérer l'ouverture de la modale",
|
|
51
|
+
type: { name: 'boolean', required: false },
|
|
52
|
+
control: { type: 'boolean' },
|
|
53
|
+
table: {
|
|
54
|
+
type: { summary: 'boolean' },
|
|
55
|
+
defaultValue: { summary: false },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
48
58
|
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { hbs } from 'ember-cli-htmlbars';
|
|
2
|
+
|
|
3
|
+
export const Template = (args) => {
|
|
4
|
+
return {
|
|
5
|
+
template: hbs`
|
|
6
|
+
<PixSidebar @showSidebar={{showSidebar}} @title={{title}} @onClose={{fn (mut showSidebar) (not showSidebar)}}>
|
|
7
|
+
<:content>
|
|
8
|
+
<p>
|
|
9
|
+
Une sidebar est, dans une interface graphique, une fenêtre qui prend le contrôle total du clavier et
|
|
10
|
+
de l'écran. Elle est en général associée à du paramétrage d'écran.
|
|
11
|
+
</p>
|
|
12
|
+
</:content>
|
|
13
|
+
<:footer>
|
|
14
|
+
<div style="display: flex; gap: 8px">
|
|
15
|
+
<PixButton @backgroundColor="transparent-light" @isBorderVisible="true">Annuler</PixButton>
|
|
16
|
+
<PixButton>Valider</PixButton>
|
|
17
|
+
</div>
|
|
18
|
+
</:footer>
|
|
19
|
+
</PixSidebar>
|
|
20
|
+
<PixButton @triggerAction={{fn (mut showSidebar) (not showSidebar)}}>Ouvrir la sidebar</PixButton>
|
|
21
|
+
`,
|
|
22
|
+
context: args,
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const Default = Template.bind({});
|
|
27
|
+
Default.args = {
|
|
28
|
+
showSidebar: true,
|
|
29
|
+
title: 'Filtrer',
|
|
30
|
+
onClose: () => {},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const argTypes = {
|
|
34
|
+
showSidebar: {
|
|
35
|
+
name: 'showSidebar',
|
|
36
|
+
description: 'Visibilité de la sidebar',
|
|
37
|
+
type: { name: 'boolean', required: false },
|
|
38
|
+
control: { type: 'boolean' },
|
|
39
|
+
table: {
|
|
40
|
+
type: { summary: 'boolean' },
|
|
41
|
+
defaultValue: { summary: false },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
title: {
|
|
45
|
+
name: 'title',
|
|
46
|
+
description: 'Titre de la sidebar',
|
|
47
|
+
type: { name: 'string', required: true },
|
|
48
|
+
table: {
|
|
49
|
+
type: { summary: 'string' },
|
|
50
|
+
defaultValue: { summary: '' },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
onClose: {
|
|
54
|
+
name: 'onClose',
|
|
55
|
+
description: 'Fonction à exécuter à la fermeture de la sidebar',
|
|
56
|
+
type: { name: 'function', required: true },
|
|
57
|
+
table: {
|
|
58
|
+
type: { summary: 'function' },
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import centered from '@storybook/addon-centered/ember';
|
|
3
|
+
|
|
4
|
+
import * as stories from './pix-sidebar.stories.js';
|
|
5
|
+
|
|
6
|
+
<Meta
|
|
7
|
+
title="Basics/Sidebar"
|
|
8
|
+
component="PixSidebar"
|
|
9
|
+
decorators={[centered]}
|
|
10
|
+
argTypes={stories.argTypes}
|
|
11
|
+
/>
|
|
12
|
+
|
|
13
|
+
# PixSidebar
|
|
14
|
+
|
|
15
|
+
Une sidebar responsive et scrollable avec un overlay.
|
|
16
|
+
|
|
17
|
+
Ce composant possède deux `yield` :
|
|
18
|
+
|
|
19
|
+
- `:content` est destiné à accueillir le contenu principal de la fenêtre sidebar. Il peut accueillir tout type de
|
|
20
|
+
contenu HTML (simple texte, image, formulaire, etc.)
|
|
21
|
+
- `:footer` peut également accueillir tout type de contenu HTML, mais est destiné aux boutons permettant d'interagir
|
|
22
|
+
avec la sidebar, ce qui permettra de les positionner correctement dans tous les cas de figure.
|
|
23
|
+
|
|
24
|
+
<Canvas>
|
|
25
|
+
<Story name="Default" story={stories.Default} height={500} />
|
|
26
|
+
</Canvas>
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<PixSidebar
|
|
32
|
+
@showSidebar={{true}}
|
|
33
|
+
@title="Filtrer"
|
|
34
|
+
@onClose={{this.closeSidebar}}
|
|
35
|
+
>
|
|
36
|
+
<:content>
|
|
37
|
+
<p>
|
|
38
|
+
Une sidebar est, dans une interface graphique, une fenêtre qui prend le contrôle total du clavier et
|
|
39
|
+
de l'écran. Elle est en général associée à du paramétrage d'écran.
|
|
40
|
+
</p>
|
|
41
|
+
</:content>
|
|
42
|
+
<:footer>
|
|
43
|
+
<PixButton
|
|
44
|
+
@backgroundColor="transparent-light"
|
|
45
|
+
@isBorderVisible={{true}}
|
|
46
|
+
@triggerAction={{this.closeSidebar}}
|
|
47
|
+
>
|
|
48
|
+
Annuler
|
|
49
|
+
</PixButton>
|
|
50
|
+
<PixButton>Valider</PixButton>
|
|
51
|
+
</:footer>
|
|
52
|
+
</PixSidebar>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Arguments
|
|
56
|
+
|
|
57
|
+
<ArgsTable story="Default" />
|