|
Before Width: | Height: | Size: 970 B After Width: | Height: | Size: 970 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 446 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1,31 @@ |
|||
'use strict'; |
|||
|
|||
import {setupResponsiveness} from './toolbar/responsive.jsx'; |
|||
|
|||
const rootAttributes = { |
|||
previewFrame: document.getElementById('preview_frame'), |
|||
} |
|||
|
|||
setupResponsiveness(rootAttributes); |
|||
|
|||
// const responsiveness = connectResponsiveness(rootAttributes);
|
|||
// setTimeout(() => responsiveness.selectMode('tablet'), 5000)
|
|||
// setTimeout(() => responsiveness.selectMode('mobile'), 10000)
|
|||
|
|||
const previewFrame = rootAttributes.previewFrame; |
|||
initDataOptions(); |
|||
|
|||
/** |
|||
* Functions |
|||
*/ |
|||
|
|||
function initDataOptions() { |
|||
const dataOptionsSelect = document.getElementById('data-options'); |
|||
if (!dataOptionsSelect) { |
|||
return; |
|||
} |
|||
|
|||
dataOptionsSelect.addEventListener('change', function () { |
|||
previewFrame.src = window.previewFrameUrl + '?data=' + this.value; |
|||
}); |
|||
} |
|||
@ -1,38 +0,0 @@ |
|||
(function () { |
|||
|
|||
const previewFrame = document.getElementById('preview_frame'); |
|||
|
|||
initDataOptions(); |
|||
initResponsiveMode(); |
|||
|
|||
/** |
|||
* Functions |
|||
*/ |
|||
|
|||
function initDataOptions() { |
|||
const dataOptionsSelect = document.getElementById('data-options'); |
|||
if (!dataOptionsSelect) { |
|||
return; |
|||
} |
|||
|
|||
dataOptionsSelect.addEventListener('change', function () { |
|||
previewFrame.src = window.previewFrameUrl + '?data=' + this.value; |
|||
}); |
|||
} |
|||
|
|||
function initResponsiveMode() { |
|||
|
|||
document.querySelectorAll('input[name="responsive_mode"]').forEach((input) => { |
|||
input.addEventListener('change', (event) => { |
|||
const mode = event.target.value; |
|||
|
|||
previewFrame.classList.remove('breakpoint--desktop'); |
|||
previewFrame.classList.remove('breakpoint--tablet'); |
|||
previewFrame.classList.remove('breakpoint--mobile'); |
|||
|
|||
previewFrame.classList.add('breakpoint--' + mode); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
})(); |
|||
|
After Width: | Height: | Size: 970 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 446 B |
|
After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1,94 @@ |
|||
import React, {useCallback, useEffect, useState} from "react"; |
|||
import {ButtonStyling, ResponsiveButtonStyle, ResponsiveOptionsDropdown} from "./responsive-button.style.js"; |
|||
|
|||
const responsiveOptions = { |
|||
desktop: [1920, 1800, 1680, 1440, 1360, 1280, 1024], |
|||
tablet: [992, 768, 600], |
|||
mobile: [480, 414, 375, 360], |
|||
} |
|||
|
|||
const defaultResponsiveOptions = { |
|||
reset: '100%', |
|||
desktop: responsiveOptions.desktop[0], |
|||
tablet: responsiveOptions.tablet[1], |
|||
mobile: responsiveOptions.mobile[2], |
|||
} |
|||
|
|||
export function ResponsiveButton({mode, active, onSelect}) { |
|||
// active = true; |
|||
const [state, setState] = useState({open: true, breakpoint: defaultResponsiveOptions[mode]}); |
|||
const updateState = (update) => { |
|||
// update.open = true; |
|||
setState(Object.assign({}, state, update)); |
|||
} |
|||
|
|||
useEffect(() => { |
|||
const closeDropdown = (e) => isEscHit(e) ? updateState({open: false}) : null; |
|||
|
|||
if (state.open) { |
|||
document.addEventListener("keydown", closeDropdown); |
|||
} |
|||
|
|||
// Unsubscribe from ESC listener. |
|||
return () => { |
|||
document.removeEventListener("keydown", closeDropdown); |
|||
} |
|||
}); |
|||
|
|||
// Blur event / Outside click |
|||
const outsideClickAction = async (e) => await isClickOutside(e) ? updateState({open: false}) : null; |
|||
const handleBlur = useCallback(outsideClickAction, []); |
|||
|
|||
return <ResponsiveButtonStyle tabIndex='0' onBlur={handleBlur}> |
|||
<ButtonStyling data-mode={mode} data-active={active} onClick={() => select()}>{mode}</ButtonStyling> |
|||
{active && state.open && responsiveOptions[mode] && |
|||
<ResponsiveOptionsDropdown> |
|||
{responsiveOptions[mode].map((breakpoint) => { |
|||
return <li> |
|||
<a className={state.breakpoint === breakpoint ? 'active' : ''} onClick={() => select(breakpoint)}>{breakpoint}</a> |
|||
</li>; |
|||
})} |
|||
</ResponsiveOptionsDropdown> |
|||
} |
|||
</ResponsiveButtonStyle>; |
|||
|
|||
// |
|||
// Actions |
|||
// |
|||
|
|||
function select(breakpoint = null) { |
|||
// Click on option in Dropdown. |
|||
if (breakpoint) { |
|||
updateState({open: false, breakpoint}); |
|||
onSelect(breakpoint); |
|||
return; |
|||
} |
|||
|
|||
// Click on device button. |
|||
if (!active) { |
|||
onSelect(state.breakpoint) |
|||
updateState({open: false}); |
|||
} else { |
|||
updateState({open: true}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
export function update(state, update) { |
|||
return Object.assign({}, state, update); |
|||
} |
|||
|
|||
export async function isClickOutside(e) { |
|||
const currentTarget = e.currentTarget; |
|||
|
|||
return new Promise(resolve => { |
|||
setTimeout(() => { |
|||
resolve(!currentTarget.contains(document.activeElement)); |
|||
}, 100); |
|||
}) |
|||
} |
|||
|
|||
export function isEscHit(event) { |
|||
return event.key === 'Escape' |
|||
} |
|||
|
|||
@ -0,0 +1,73 @@ |
|||
import styled from "styled-components"; |
|||
|
|||
export const ButtonStyling = styled.button` |
|||
--size: 1.5rem; |
|||
cursor: pointer; |
|||
border: 0; |
|||
background-image: url("/scripts/dist/toolbar/images/icon-desktop.svg"); |
|||
background-repeat: no-repeat; |
|||
background-size: calc(var(--size) - 0.15rem); |
|||
background-position: center; |
|||
background-color: initial; |
|||
font-size: 1px; |
|||
color: rgba(0, 0, 0, 0); |
|||
line-height: 1; |
|||
display: block; |
|||
width: var(--size); |
|||
height: var(--size); |
|||
border-radius: 0.25rem; |
|||
outline: none; |
|||
|
|||
&[data-mode='tablet'] { |
|||
background-image: url("/scripts/dist/toolbar/images/icon-tablet.svg"); |
|||
} |
|||
|
|||
&[data-mode='mobile'] { |
|||
background-image: url("/scripts/dist/toolbar/images/icon-mobile.svg"); |
|||
} |
|||
|
|||
&[data-mode='reset'] { |
|||
background-image: url("/scripts/dist/toolbar/images/icon-reset.svg"); |
|||
} |
|||
|
|||
&[data-active='true'] { |
|||
background-color: #CBD5E0; |
|||
} |
|||
`;
|
|||
|
|||
export const ResponsiveOptionsDropdown = styled.ul` |
|||
list-style: none; |
|||
padding: 4px; |
|||
position: absolute; |
|||
background-color: white; |
|||
border: 1px solid rgba(0, 0, 0, 0.25); |
|||
box-shadow: 2px 2px 4px 0 #ccc; |
|||
border-radius: 4px; |
|||
transform: translateX(-50%); |
|||
left: 50%; |
|||
margin: 0.25rem 0 0; |
|||
|
|||
li { |
|||
margin-bottom: 2px; |
|||
|
|||
a { |
|||
display: block; |
|||
padding: 0.5rem 1rem; |
|||
cursor: pointer; |
|||
color: #14181F; |
|||
border-radius: 4px; |
|||
|
|||
&:hover, &:focus { |
|||
background-color: #EDF2F7; |
|||
} |
|||
|
|||
&.active { |
|||
background-color: #cbd5e0; |
|||
} |
|||
} |
|||
} |
|||
`;
|
|||
|
|||
export const ResponsiveButtonStyle = styled.div` |
|||
position: relative; |
|||
`;
|
|||
@ -0,0 +1,81 @@ |
|||
// export function connectResponsiveness(rootAttributes) { |
|||
// // API |
|||
// return { |
|||
// selectMode: (mode) => selectMode(mode), |
|||
// } |
|||
// } |
|||
// |
|||
|
|||
import React, {useEffect, useState} from 'react'; |
|||
import * as ReactDOM from 'react-dom/client'; |
|||
import {WrapperStyling} from "./responsive.style.js"; |
|||
import {ResponsiveButton} from "./responsive-button/ResponsiveButton.jsx"; |
|||
|
|||
const modes = [ |
|||
'reset', |
|||
'desktop', |
|||
'tablet', |
|||
'mobile' |
|||
]; |
|||
|
|||
function Responsive(props = {}) { |
|||
props.rootAttributes = props.rootAttributes ?? {}; |
|||
|
|||
const initialState = {mode: 'default', breakpoint: '100%'} |
|||
|
|||
const [state, setState] = useState(initialState); |
|||
const updateState = (update) => setState(Object.assign({}, state, update)); |
|||
|
|||
useEffect(() => { |
|||
// Update the document title using the browser API |
|||
updateController(state); |
|||
}); |
|||
|
|||
const previewFrame = props.rootAttributes.previewFrame; |
|||
return render(); |
|||
|
|||
// |
|||
// Functions |
|||
// |
|||
|
|||
function render() { |
|||
return <WrapperStyling> |
|||
{modes.map((mode) => { |
|||
return <ResponsiveButton mode={mode} |
|||
active={isActive(mode)} |
|||
onSelect={(breakpoint) => selectMode(mode, breakpoint)}/> |
|||
})} |
|||
</WrapperStyling>; |
|||
} |
|||
|
|||
function selectMode(mode, breakpoint) { |
|||
if (mode === 'reset') { |
|||
updateState(initialState); |
|||
return; |
|||
} |
|||
|
|||
updateState({mode, breakpoint}) |
|||
} |
|||
|
|||
|
|||
function isActive(mode) { |
|||
return mode === state.mode; |
|||
} |
|||
|
|||
function updateController() { |
|||
const unit = typeof state.breakpoint === 'string' ? '' : 'px'; |
|||
previewFrame.style.setProperty('--breakpoint', state.breakpoint + unit); |
|||
previewFrame.classList.add('has-breakpoint'); |
|||
} |
|||
|
|||
} |
|||
|
|||
export function setupResponsiveness(rootAttributes) { |
|||
// INIT |
|||
const wrapper = document.createElement('div'); |
|||
document.querySelector('.page_toolbar').prepend(wrapper) |
|||
|
|||
const root = ReactDOM.createRoot(wrapper); |
|||
const html = (<Responsive rootAttributes={rootAttributes}/>); |
|||
root.render(html); |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
import styled from "styled-components"; |
|||
|
|||
export const WrapperStyling = styled.div` |
|||
display: flex; |
|||
gap: 0.5rem; |
|||
`;
|
|||
@ -1,38 +0,0 @@ |
|||
|
|||
&__responsive_mode { |
|||
display: flex; |
|||
gap: 0.5rem; |
|||
|
|||
input[type='radio'] { |
|||
display: none; |
|||
} |
|||
|
|||
label { |
|||
--size: 1.5rem; |
|||
cursor: pointer; |
|||
background-image: url("../images/icon-desktop.svg"); |
|||
background-repeat: no-repeat; |
|||
background-size: calc(var(--size) - 0.15rem); |
|||
background-position: center; |
|||
font-size: 1px; |
|||
color: rgba(0, 0, 0, 0); |
|||
line-height: 1; |
|||
display: block; |
|||
width: var(--size); |
|||
height: var(--size); |
|||
border-radius: 0.25rem; |
|||
|
|||
&[for="responsive_mode__tablet"] { |
|||
background-image: url("../images/icon-tablet.svg"); |
|||
} |
|||
|
|||
&[for="responsive_mode__mobile"] { |
|||
background-image: url("../images/icon-mobile.svg"); |
|||
} |
|||
} |
|||
|
|||
input[type='radio']:checked + label { |
|||
background-color: #CBD5E0; |
|||
} |
|||
|
|||
} |
|||
@ -1 +1 @@ |
|||
{"version":3,"sourceRoot":"","sources":["page--main.scss","_page--main--responsive-mode.scss","_page--breakpoints.scss"],"names":[],"mappings":"AAAA;EACE;EACA;EAEA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;EACA;;AAGA;EACE;;AAEA;EAHF;IAII;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAKF;EADF;IAEI;;;ACxDN;EACE;EACA;;AAEA;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAIJ;EACE;;;AClCJ;EACE;EACA;EACA;EAEA;EAEA;;AAEA;EACE;EACA;;AAGA;EACE;;AAGF;EACE","file":"page--main.css"} |
|||
{"version":3,"sourceRoot":"","sources":["page--main.scss","_page--breakpoints.scss"],"names":[],"mappings":"AAAA;EACE;EACA;EAEA;;;AAGF;EACE;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;EACA;;AAGA;EACE;;AAEA;EAHF;IAII;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAKF;EADF;IAEI;;;;ACnDN;EACE;EACA;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;EAEA;EAEA;;AAEA;EACE;EAEA;EACA;EACA","file":"page--main.css"} |
|||
@ -0,0 +1,35 @@ |
|||
import babel from '@rollup/plugin-babel'; |
|||
import {nodeResolve} from '@rollup/plugin-node-resolve'; |
|||
import commonjs from '@rollup/plugin-commonjs'; |
|||
import replace from '@rollup/plugin-replace'; |
|||
import css from "@modular-css/rollup"; |
|||
import copy from 'rollup-plugin-copy' |
|||
|
|||
export default { |
|||
input: 'layouts/scripts/index.js', |
|||
output: { |
|||
file: 'layouts/scripts/dist/index.min.js', |
|||
sourcemap: true |
|||
}, |
|||
plugins: [ |
|||
nodeResolve({ |
|||
extensions: [".js"], |
|||
}), |
|||
replace({ |
|||
'process.env.NODE_ENV': JSON.stringify('production'), |
|||
preventAssignment: true, |
|||
}), |
|||
css(), |
|||
babel({ |
|||
compact: false, |
|||
babelHelpers: 'bundled', |
|||
presets: ["@babel/preset-react"], |
|||
}), |
|||
commonjs(), |
|||
copy({ |
|||
targets: [ |
|||
{ src: 'layouts/scripts/toolbar/images/**/*', dest: 'layouts/scripts/dist/toolbar/images' } |
|||
] |
|||
}) |
|||
], |
|||
} |
|||