@plone/volto 17.0.0-alpha.1 → 17.0.0-alpha.3
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.draft +9 -18
- package/.vale.ini +10 -0
- package/.yarn/install-state.gz +0 -0
- package/CHANGELOG.md +114 -3
- package/README.md +4 -4
- package/cypress/support/commands.js +25 -0
- package/locales/ca/LC_MESSAGES/volto.po +6 -1
- package/locales/ca.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +6 -1
- package/locales/de.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +6 -1
- package/locales/en.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +6 -1
- package/locales/es.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +6 -1
- package/locales/eu.json +1 -1
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +6 -1
- package/locales/fr.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +6 -1
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +6 -1
- package/locales/ja.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +6 -1
- package/locales/nl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +6 -1
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +6 -1
- package/locales/pt_BR.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +6 -1
- package/locales/ro.json +1 -1
- package/locales/volto.pot +6 -1
- package/locales/zh_CN/LC_MESSAGES/volto.po +6 -1
- package/locales/zh_CN.json +1 -1
- package/package-why.json +0 -1
- package/package.json +7 -7
- package/packages/volto-slate/package.json +1 -1
- package/packages/volto-slate/src/blocks/Table/index.js +2 -0
- package/src/components/manage/BlockChooser/BlockChooser.jsx +6 -2
- package/src/components/manage/Blocks/Listing/Edit.jsx +0 -5
- package/src/components/manage/Blocks/Listing/ListingBody.jsx +77 -61
- package/src/components/manage/Blocks/Listing/View.jsx +0 -4
- package/src/components/manage/Blocks/ToC/Edit.jsx +1 -0
- package/src/components/manage/Contents/Contents.jsx +35 -8
- package/src/components/manage/Controlpanels/AddonsControlpanel.jsx +3 -3
- package/src/components/manage/Controlpanels/Controlpanels.jsx +1 -1
- package/src/components/manage/DragDropList/DragDropList.jsx +63 -42
- package/src/components/manage/Form/BlocksToolbar.jsx +5 -1
- package/src/components/manage/Form/Form.jsx +6 -2
- package/src/helpers/Url/Url.js +19 -3
- package/src/helpers/Url/Url.test.js +12 -0
- package/styles/Vocab/Base/accept.txt +0 -0
- package/styles/Vocab/Base/reject.txt +0 -0
- package/styles/Vocab/Plone/accept.txt +8 -0
- package/styles/Vocab/Plone/reject.txt +0 -0
- package/theme/themes/pastanaga/extras/contents.less +1 -0
- package/theme/themes/pastanaga/extras/main.less +80 -1
|
@@ -290,6 +290,10 @@ const messages = defineMessages({
|
|
|
290
290
|
id: 'This Page is referenced by the following items:',
|
|
291
291
|
defaultMessage: 'This Page is referenced by the following items:',
|
|
292
292
|
},
|
|
293
|
+
deleteItemCountMessage: {
|
|
294
|
+
id: 'Total items to be deleted:',
|
|
295
|
+
defaultMessage: 'Total items to be deleted:',
|
|
296
|
+
},
|
|
293
297
|
deleteItemMessage: {
|
|
294
298
|
id: 'Items to be deleted:',
|
|
295
299
|
defaultMessage: 'Items to be deleted:',
|
|
@@ -418,6 +422,8 @@ class Contents extends Component {
|
|
|
418
422
|
this.paste = this.paste.bind(this);
|
|
419
423
|
this.fetchContents = this.fetchContents.bind(this);
|
|
420
424
|
this.orderTimeout = null;
|
|
425
|
+
this.deleteItemsToShowThreshold = 10;
|
|
426
|
+
|
|
421
427
|
this.state = {
|
|
422
428
|
selected: [],
|
|
423
429
|
showDelete: false,
|
|
@@ -427,6 +433,7 @@ class Contents extends Component {
|
|
|
427
433
|
showProperties: false,
|
|
428
434
|
showWorkflow: false,
|
|
429
435
|
itemsToDelete: [],
|
|
436
|
+
showAllItemsToDelete: true,
|
|
430
437
|
items: this.props.items,
|
|
431
438
|
filter: '',
|
|
432
439
|
currentPage: 0,
|
|
@@ -456,7 +463,6 @@ class Contents extends Component {
|
|
|
456
463
|
this.fetchContents();
|
|
457
464
|
this.setState({ isClient: true });
|
|
458
465
|
}
|
|
459
|
-
|
|
460
466
|
async componentDidUpdate(_, prevState) {
|
|
461
467
|
if (
|
|
462
468
|
this.state.itemsToDelete !== prevState.itemsToDelete &&
|
|
@@ -468,6 +474,8 @@ class Contents extends Component {
|
|
|
468
474
|
this.getFieldById(item, 'UID'),
|
|
469
475
|
),
|
|
470
476
|
),
|
|
477
|
+
showAllItemsToDelete:
|
|
478
|
+
this.state.itemsToDelete.length < this.deleteItemsToShowThreshold,
|
|
471
479
|
});
|
|
472
480
|
}
|
|
473
481
|
}
|
|
@@ -1188,16 +1196,35 @@ class Contents extends Component {
|
|
|
1188
1196
|
<div className="content">
|
|
1189
1197
|
<h3>
|
|
1190
1198
|
{this.props.intl.formatMessage(
|
|
1191
|
-
messages.
|
|
1192
|
-
)}
|
|
1199
|
+
messages.deleteItemCountMessage,
|
|
1200
|
+
) + ` ${this.state.itemsToDelete.length}`}
|
|
1193
1201
|
</h3>
|
|
1194
1202
|
<ul className="content">
|
|
1195
|
-
{map(
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1203
|
+
{map(
|
|
1204
|
+
this.state.showAllItemsToDelete
|
|
1205
|
+
? this.state.itemsToDelete
|
|
1206
|
+
: this.state.itemsToDelete.slice(
|
|
1207
|
+
0,
|
|
1208
|
+
this.deleteItemsToShowThreshold,
|
|
1209
|
+
),
|
|
1210
|
+
(item) => (
|
|
1211
|
+
<li key={item}>
|
|
1212
|
+
{this.getFieldById(item, 'title')}
|
|
1213
|
+
</li>
|
|
1214
|
+
),
|
|
1215
|
+
)}
|
|
1200
1216
|
</ul>
|
|
1217
|
+
{!this.state.showAllItemsToDelete && (
|
|
1218
|
+
<Button
|
|
1219
|
+
onClick={() =>
|
|
1220
|
+
this.setState({
|
|
1221
|
+
showAllItemsToDelete: true,
|
|
1222
|
+
})
|
|
1223
|
+
}
|
|
1224
|
+
>
|
|
1225
|
+
Show all items
|
|
1226
|
+
</Button>
|
|
1227
|
+
)}
|
|
1201
1228
|
{this.state.linkIntegrityBreakages.length > 0 ? (
|
|
1202
1229
|
<div>
|
|
1203
1230
|
<h3>
|
|
@@ -43,7 +43,7 @@ const messages = defineMessages({
|
|
|
43
43
|
addAddons: {
|
|
44
44
|
id: 'Add Addons',
|
|
45
45
|
defaultMessage:
|
|
46
|
-
'To make new add-ons show up here, add them to your
|
|
46
|
+
'To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see',
|
|
47
47
|
},
|
|
48
48
|
addonsSettings: {
|
|
49
49
|
id: 'Add-ons Settings',
|
|
@@ -380,11 +380,11 @@ class AddonsControlpanel extends Component {
|
|
|
380
380
|
</Header>
|
|
381
381
|
<FormattedMessage
|
|
382
382
|
id="Add Addons"
|
|
383
|
-
defaultMessage="To make new add-ons show up here, add them to your
|
|
383
|
+
defaultMessage="To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see"
|
|
384
384
|
/>
|
|
385
385
|
|
|
386
386
|
<a
|
|
387
|
-
href="
|
|
387
|
+
href="https://6.docs.plone.org/install/"
|
|
388
388
|
target="_blank"
|
|
389
389
|
rel="noopener noreferrer"
|
|
390
390
|
>
|
|
@@ -270,7 +270,7 @@ class Controlpanels extends Component {
|
|
|
270
270
|
{group}
|
|
271
271
|
</Segment>,
|
|
272
272
|
<Segment key={`body-${group}`} attached>
|
|
273
|
-
<Grid columns={6}>
|
|
273
|
+
<Grid doubling columns={6}>
|
|
274
274
|
<Grid.Row>
|
|
275
275
|
{map(filter(controlpanels, { group }), (controlpanel) => (
|
|
276
276
|
<Grid.Column key={controlpanel.id}>
|
|
@@ -3,7 +3,7 @@ import { isEmpty } from 'lodash';
|
|
|
3
3
|
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
|
|
4
4
|
import { v4 as uuid } from 'uuid';
|
|
5
5
|
|
|
6
|
-
const getPlaceholder = (draggedDOM, sourceIndex, destinationIndex) => {
|
|
6
|
+
const getPlaceholder = (draggedDOM, sourceIndex, destinationIndex, uid) => {
|
|
7
7
|
// Because of the margin rendering rules, there is no easy
|
|
8
8
|
// way to calculate the offset of the placeholder.
|
|
9
9
|
//
|
|
@@ -13,12 +13,16 @@ const getPlaceholder = (draggedDOM, sourceIndex, destinationIndex) => {
|
|
|
13
13
|
//
|
|
14
14
|
// To get a placeholder that looks good in all cases, we
|
|
15
15
|
// fill up the space between the previous and the next element.
|
|
16
|
-
const
|
|
16
|
+
const queryAttr = 'data-rbd-droppable-id';
|
|
17
|
+
const domQuery = `[${queryAttr}='${uid}']`;
|
|
18
|
+
const parentDOM = document.querySelector(domQuery);
|
|
19
|
+
|
|
20
|
+
const childrenArray = [...parentDOM.children];
|
|
17
21
|
// Remove the source element
|
|
18
22
|
childrenArray.splice(sourceIndex, 1);
|
|
19
23
|
// Also remove the placeholder that the library always inserts at the end
|
|
20
24
|
childrenArray.splice(-1, 1);
|
|
21
|
-
const parentRect =
|
|
25
|
+
const parentRect = parentDOM.getBoundingClientRect();
|
|
22
26
|
const prevNode = childrenArray[destinationIndex - 1];
|
|
23
27
|
const nextNode = childrenArray[destinationIndex];
|
|
24
28
|
let top, bottom;
|
|
@@ -40,9 +44,7 @@ const getPlaceholder = (draggedDOM, sourceIndex, destinationIndex) => {
|
|
|
40
44
|
return {
|
|
41
45
|
clientY: top,
|
|
42
46
|
clientHeight: bottom - top,
|
|
43
|
-
clientX: parseFloat(
|
|
44
|
-
window.getComputedStyle(draggedDOM.parentNode).paddingLeft,
|
|
45
|
-
),
|
|
47
|
+
clientX: parseFloat(window.getComputedStyle(parentDOM).paddingLeft),
|
|
46
48
|
clientWidth: draggedDOM.clientWidth,
|
|
47
49
|
};
|
|
48
50
|
};
|
|
@@ -63,17 +65,22 @@ const DragDropList = (props) => {
|
|
|
63
65
|
// queueing timed action
|
|
64
66
|
const timer = useRef(null);
|
|
65
67
|
|
|
66
|
-
const onDragStart = React.useCallback(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
68
|
+
const onDragStart = React.useCallback(
|
|
69
|
+
(event) => {
|
|
70
|
+
clearTimeout(timer.current);
|
|
71
|
+
const queryAttr = 'data-rbd-draggable-id';
|
|
72
|
+
const domQuery = `[${queryAttr}='${event.draggableId}']`;
|
|
73
|
+
const draggedDOM = document.querySelector(domQuery);
|
|
74
|
+
if (!draggedDOM) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const sourceIndex = event.source.index;
|
|
78
|
+
setPlaceholderProps(
|
|
79
|
+
getPlaceholder(draggedDOM, sourceIndex, sourceIndex, uid),
|
|
80
|
+
);
|
|
81
|
+
},
|
|
82
|
+
[uid],
|
|
83
|
+
);
|
|
77
84
|
|
|
78
85
|
const onDragEnd = React.useCallback(
|
|
79
86
|
(result) => {
|
|
@@ -84,30 +91,33 @@ const DragDropList = (props) => {
|
|
|
84
91
|
[onMoveItem],
|
|
85
92
|
);
|
|
86
93
|
|
|
87
|
-
const onDragUpdate = React.useCallback(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
94
|
+
const onDragUpdate = React.useCallback(
|
|
95
|
+
(update) => {
|
|
96
|
+
clearTimeout(timer.current);
|
|
97
|
+
setPlaceholderProps({});
|
|
98
|
+
if (!update.destination) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const draggableId = update.draggableId;
|
|
102
|
+
const queryAttr = 'data-rbd-draggable-id';
|
|
103
|
+
const domQuery = `[${queryAttr}='${draggableId}']`;
|
|
104
|
+
const draggedDOM = document.querySelector(domQuery);
|
|
105
|
+
if (!draggedDOM) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const sourceIndex = update.source.index;
|
|
109
|
+
const destinationIndex = update.destination.index;
|
|
110
|
+
// Wait until the animations have finished, to make it look good.
|
|
111
|
+
timer.current = setTimeout(
|
|
112
|
+
() =>
|
|
113
|
+
setPlaceholderProps(
|
|
114
|
+
getPlaceholder(draggedDOM, sourceIndex, destinationIndex, uid),
|
|
115
|
+
),
|
|
116
|
+
250,
|
|
117
|
+
);
|
|
118
|
+
},
|
|
119
|
+
[uid],
|
|
120
|
+
);
|
|
111
121
|
|
|
112
122
|
const AsDomComponent = as;
|
|
113
123
|
return (
|
|
@@ -116,7 +126,18 @@ const DragDropList = (props) => {
|
|
|
116
126
|
onDragUpdate={onDragUpdate}
|
|
117
127
|
onDragEnd={onDragEnd}
|
|
118
128
|
>
|
|
119
|
-
<Droppable
|
|
129
|
+
<Droppable
|
|
130
|
+
droppableId={uid}
|
|
131
|
+
renderClone={(provided, snapshot, rubric) => {
|
|
132
|
+
const index = rubric.source.index;
|
|
133
|
+
return children({
|
|
134
|
+
child: childList[index][1],
|
|
135
|
+
childId: childList[index][0],
|
|
136
|
+
index,
|
|
137
|
+
draginfo: provided,
|
|
138
|
+
});
|
|
139
|
+
}}
|
|
140
|
+
>
|
|
120
141
|
{(provided, snapshot) => (
|
|
121
142
|
<AsDomComponent
|
|
122
143
|
ref={provided.innerRef}
|
|
@@ -177,7 +177,11 @@ export class BlocksToolbarComponent extends React.Component {
|
|
|
177
177
|
''
|
|
178
178
|
)}
|
|
179
179
|
{selectedBlock && (blocksClipboard?.cut || blocksClipboard?.copy) && (
|
|
180
|
-
<Plug
|
|
180
|
+
<Plug
|
|
181
|
+
pluggable="main.toolbar.bottom"
|
|
182
|
+
id="block-paste-btn"
|
|
183
|
+
dependencies={[selectedBlock]}
|
|
184
|
+
>
|
|
181
185
|
<button
|
|
182
186
|
aria-label={intl.formatMessage(messages.pasteBlocks)}
|
|
183
187
|
onClick={this.pasteBlocks}
|
|
@@ -258,7 +258,11 @@ class Form extends Component {
|
|
|
258
258
|
* Tab selection is done only by setting activeIndex in state
|
|
259
259
|
*/
|
|
260
260
|
onTabChange(e, { activeIndex }) {
|
|
261
|
-
this.
|
|
261
|
+
const defaultFocus = this.props.schema.fieldsets[activeIndex].fields[0];
|
|
262
|
+
this.setState({
|
|
263
|
+
activeIndex,
|
|
264
|
+
...(defaultFocus ? { inFocus: { [defaultFocus]: true } } : {}),
|
|
265
|
+
});
|
|
262
266
|
}
|
|
263
267
|
|
|
264
268
|
/**
|
|
@@ -686,7 +690,7 @@ class Form extends Component {
|
|
|
686
690
|
id={field}
|
|
687
691
|
formData={this.state.formData}
|
|
688
692
|
fieldSet={item.title.toLowerCase()}
|
|
689
|
-
focus={
|
|
693
|
+
focus={this.state.inFocus[field]}
|
|
690
694
|
value={this.state.formData?.[field]}
|
|
691
695
|
required={schema.required.indexOf(field) !== -1}
|
|
692
696
|
onChange={this.onChangeField}
|
package/src/helpers/Url/Url.js
CHANGED
|
@@ -7,6 +7,7 @@ import { last, memoize } from 'lodash';
|
|
|
7
7
|
import { urlRegex, telRegex, mailRegex } from './urlRegex';
|
|
8
8
|
import prependHttp from 'prepend-http';
|
|
9
9
|
import config from '@plone/volto/registry';
|
|
10
|
+
import { matchPath } from 'react-router';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Get base url.
|
|
@@ -213,7 +214,17 @@ export function expandToBackendURL(path) {
|
|
|
213
214
|
*/
|
|
214
215
|
export function isInternalURL(url) {
|
|
215
216
|
const { settings } = config;
|
|
216
|
-
|
|
217
|
+
|
|
218
|
+
const isMatch = (config.settings.externalRoutes ?? []).find((route) => {
|
|
219
|
+
if (typeof route === 'object') {
|
|
220
|
+
return matchPath(flattenToAppURL(url), route.match);
|
|
221
|
+
}
|
|
222
|
+
return matchPath(flattenToAppURL(url), route);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const isExcluded = isMatch && Object.keys(isMatch)?.length > 0;
|
|
226
|
+
|
|
227
|
+
const internalURL =
|
|
217
228
|
url &&
|
|
218
229
|
(url.indexOf(settings.publicURL) !== -1 ||
|
|
219
230
|
(settings.internalApiPath &&
|
|
@@ -221,8 +232,13 @@ export function isInternalURL(url) {
|
|
|
221
232
|
url.indexOf(settings.apiPath) !== -1 ||
|
|
222
233
|
url.charAt(0) === '/' ||
|
|
223
234
|
url.charAt(0) === '.' ||
|
|
224
|
-
url.startsWith('#'))
|
|
225
|
-
|
|
235
|
+
url.startsWith('#'));
|
|
236
|
+
|
|
237
|
+
if (internalURL && isExcluded) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return internalURL;
|
|
226
242
|
}
|
|
227
243
|
|
|
228
244
|
/**
|
|
@@ -191,6 +191,7 @@ describe('Url', () => {
|
|
|
191
191
|
expect(isInternalURL(href)).toBe(false);
|
|
192
192
|
settings.internalApiPath = saved;
|
|
193
193
|
});
|
|
194
|
+
|
|
194
195
|
it('tells if an URL is internal if it is an anchor', () => {
|
|
195
196
|
const href = '#anchor';
|
|
196
197
|
expect(isInternalURL(href)).toBe(true);
|
|
@@ -211,6 +212,17 @@ describe('Url', () => {
|
|
|
211
212
|
const href = undefined;
|
|
212
213
|
expect(isInternalURL(href)).toBe(undefined);
|
|
213
214
|
});
|
|
215
|
+
it('tells if an URL is external if settings.externalroutes is persent.', () => {
|
|
216
|
+
const url = `https://localhost:3000/fb/my-page/contents`;
|
|
217
|
+
const blacklistedurl = '/blacklisted';
|
|
218
|
+
settings.externalRoutes = [
|
|
219
|
+
{ title: 'My Page', match: '/fb' },
|
|
220
|
+
'/blacklisted',
|
|
221
|
+
];
|
|
222
|
+
settings.publicURL = 'https://localhost:3000';
|
|
223
|
+
expect(isInternalURL(url)).toBe(false);
|
|
224
|
+
expect(isInternalURL(blacklistedurl)).toBe(false);
|
|
225
|
+
});
|
|
214
226
|
});
|
|
215
227
|
describe('isUrl', () => {
|
|
216
228
|
it('isUrl test', () => {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
@import (multiple) '../../theme.config';
|
|
9
9
|
|
|
10
10
|
// Extras (third party libs)
|
|
11
|
-
@import (less) '~hamburgers/dist/hamburgers.css';
|
|
12
11
|
@import (less) '~react-toastify/dist/ReactToastify.css';
|
|
13
12
|
|
|
14
13
|
// Mixins
|
|
@@ -494,6 +493,86 @@ fieldset.invisible {
|
|
|
494
493
|
.hamburger-wrapper {
|
|
495
494
|
position: relative;
|
|
496
495
|
z-index: 5;
|
|
496
|
+
width: 70px;
|
|
497
|
+
height: 59px;
|
|
498
|
+
padding: 15px 15px;
|
|
499
|
+
|
|
500
|
+
.hamburger {
|
|
501
|
+
position: relative;
|
|
502
|
+
width: 40px;
|
|
503
|
+
height: 24px;
|
|
504
|
+
padding: 0;
|
|
505
|
+
border: none;
|
|
506
|
+
background-color: transparent;
|
|
507
|
+
cursor: pointer;
|
|
508
|
+
transition: 0.3s ease-in-out;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.hamburger::after {
|
|
512
|
+
position: absolute;
|
|
513
|
+
top: 0;
|
|
514
|
+
left: 0;
|
|
515
|
+
display: inline-block;
|
|
516
|
+
width: 100%;
|
|
517
|
+
height: 4px;
|
|
518
|
+
background-color: #000;
|
|
519
|
+
border-radius: 4px;
|
|
520
|
+
content: '';
|
|
521
|
+
transition: 0.3s ease-in-out;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.hamburger::before {
|
|
525
|
+
position: absolute;
|
|
526
|
+
top: 20px;
|
|
527
|
+
left: 0;
|
|
528
|
+
display: inline-block;
|
|
529
|
+
width: 100%;
|
|
530
|
+
height: 4px;
|
|
531
|
+
background-color: #000;
|
|
532
|
+
border-radius: 4px;
|
|
533
|
+
content: '';
|
|
534
|
+
transition: 0.3s ease-in-out;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.hamburger-inner {
|
|
538
|
+
position: absolute;
|
|
539
|
+
top: 10px;
|
|
540
|
+
left: 0;
|
|
541
|
+
display: inline-block;
|
|
542
|
+
width: 100%;
|
|
543
|
+
height: 4px;
|
|
544
|
+
border-radius: 4px;
|
|
545
|
+
transition: 0.3s ease-in-out;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.hamburger.is-active::after,
|
|
549
|
+
.hamburger.is-active::before {
|
|
550
|
+
top: 10px;
|
|
551
|
+
left: 50%;
|
|
552
|
+
width: 0%;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
.hamburger-inner::after,
|
|
556
|
+
.hamburger-inner::before {
|
|
557
|
+
position: absolute;
|
|
558
|
+
top: 0;
|
|
559
|
+
left: 0;
|
|
560
|
+
display: block;
|
|
561
|
+
width: 100%;
|
|
562
|
+
height: 4px;
|
|
563
|
+
background-color: #000;
|
|
564
|
+
border-radius: 4px;
|
|
565
|
+
content: '';
|
|
566
|
+
transition: 0.3s ease-in-out;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.hamburger.is-active .hamburger-inner::after {
|
|
570
|
+
transform: rotate(45deg);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.hamburger.is-active .hamburger-inner::before {
|
|
574
|
+
transform: rotate(-45deg);
|
|
575
|
+
}
|
|
497
576
|
}
|
|
498
577
|
|
|
499
578
|
.mobile-menu {
|