-
Notifications
You must be signed in to change notification settings - Fork 591
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pro 6518 mobile preview #4720
base: main
Are you sure you want to change the base?
Pro 6518 mobile preview #4720
Changes from 17 commits
38e5020
be578e8
60de087
4c20516
62fd03a
c9579c8
895fb42
7db9493
8991290
6806ded
174c897
4573f98
e52a478
bf528e8
ca2bb37
c296953
e6e4cd1
52c8cb3
129cc3a
0ac0e49
f7eaa02
070fc88
1329f7e
64e6c27
5b8b7a3
b2c051a
ec60630
bffb915
c93db00
180a12d
3b887ff
05f0f75
0d072dd
d08b8fd
f86f60a
7da9500
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
<template> | ||
<div | ||
data-apos-test="devicePreviewMode" | ||
class="apos-admin-bar__device-preview-mode" | ||
> | ||
<component | ||
:is="'AposButton'" | ||
v-for="(screen, name) in screens" | ||
:key="name" | ||
:data-apos-test="`devicePreviewMode:${name}`" | ||
:modifiers="['small', 'no-motion']" | ||
:label="screen.label" | ||
:icon="screen.icon" | ||
:icon-only="true" | ||
type="subtle" | ||
class="apos-admin-bar__device-preview-mode-button" | ||
:class="{ 'apos-is-active': mode === name }" | ||
@click="toggleDevicePreviewMode({ mode: name, width: screen.minWidth })" | ||
/> | ||
</div> | ||
</template> | ||
<script> | ||
|
||
export default { | ||
name: 'TheAposContextDevicePreview', | ||
props: { | ||
// { screenName: { label: string, minWidth: number, icon: string } } | ||
screens: { | ||
type: Object, | ||
validator(value, props) { | ||
return Object.values(value).every(screen => | ||
typeof screen.label === 'string' && | ||
typeof screen.minWidth === 'number' && | ||
typeof screen.icon === 'string' | ||
); | ||
}, | ||
default: () => { | ||
return {}; | ||
} | ||
}, | ||
resizable: { | ||
type: Boolean, | ||
default: false | ||
} | ||
}, | ||
emits: [ 'switch-device-preview-mode', 'reset-device-preview-mode' ], | ||
data() { | ||
return { | ||
mode: null | ||
}; | ||
}, | ||
mounted() { | ||
apos.bus.$on('command-menu-admin-bar-toggle-device-preview-mode', this.toggleDevicePreviewMode); | ||
}, | ||
unmounted() { | ||
apos.bus.$off('command-menu-admin-bar-toggle-device-preview-mode', this.toggleDevicePreviewMode); | ||
}, | ||
methods: { | ||
switchDevicePreviewMode({ mode, width }) { | ||
document.querySelector('[data-apos-refreshable]').setAttribute('device-preview-mode', mode); | ||
document.querySelector('[data-apos-refreshable]').setAttribute('resizable', this.resizable); | ||
document.querySelector('[data-apos-refreshable]').style.width = `${width}px`; | ||
this.mode = mode; | ||
this.$emit('switch-device-preview-mode', mode, width); | ||
}, | ||
toggleDevicePreviewMode({ mode, width }) { | ||
if (this.mode === mode || mode === null) { | ||
document.querySelector('[data-apos-refreshable]').removeAttribute('device-preview-mode'); | ||
document.querySelector('[data-apos-refreshable]').style.removeProperty('width'); | ||
this.mode = null; | ||
this.$emit('reset-device-preview-mode'); | ||
|
||
return; | ||
} | ||
|
||
this.switchDevicePreviewMode({ | ||
mode, | ||
width | ||
}); | ||
} | ||
} | ||
}; | ||
</script> | ||
<style lang="scss" scoped> | ||
.apos-admin-bar__device-preview-mode { | ||
margin-left: 22px; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. round to 20px (base * 2) |
||
} | ||
|
||
.apos-admin-bar__device-preview-mode-button { | ||
& + & { | ||
margin-left: 6px; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Round to 5px (base / 2) .. |
||
} | ||
|
||
&.apos-is-active { | ||
color: var(--a-text-primary); | ||
text-decoration: none; | ||
background-color: var(--a-base-10); | ||
border-radius: var(--a-border-radius); | ||
outline: 1px solid var(--a-base-7); | ||
} | ||
} | ||
</style> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,7 +44,42 @@ module.exports = { | |
rebundleModules: undefined, | ||
// In case of external front end like Astro, this option allows to | ||
// disable the build of the public UI assets. | ||
publicBundle: true | ||
publicBundle: true, | ||
// Device preview in the admin UI. | ||
// NOTE: the whole devicePreviewMode option must be carried over | ||
// to the project for override to work properly. | ||
// Nested object options are not deep merged in Apostrophe. | ||
devicePreviewMode: { | ||
// Enable device preview mode | ||
enable: false, | ||
// Warn during build about unsupported media queries. | ||
debug: false, | ||
// Breakpoints | ||
breakpoints: { | ||
// If we can resize the preview container? | ||
resizable: false, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move this up a level? |
||
// Screen list with icons | ||
// For adding icons, please refer to the icons documentation | ||
// https://docs.apostrophecms.org/reference/module-api/module-overview.html#icons | ||
screens: { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. breakpoints = screens? |
||
desktop: { | ||
label: 'apostrophe:devicePreviewDesktop', | ||
minWidth: 1024, | ||
icon: 'monitor-icon' | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These need to be able to support heights? |
||
tablet: { | ||
label: 'apostrophe:devicePreviewTablet', | ||
minWidth: 640, | ||
icon: 'tablet-icon' | ||
}, | ||
phone: { | ||
label: 'apostrophe:devicePreviewPhone', | ||
minWidth: 480, | ||
icon: 'cellphone-icon' | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
|
||
async init(self) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,32 @@ | ||
const path = require('path'); | ||
|
||
module.exports = (options, apos) => { | ||
const mediaToContainerQueriesLoader = apos.asset.options.devicePreviewMode?.enable === true | ||
? { | ||
loader: path.resolve(__dirname, '../media-to-container-queries-loader.js'), | ||
options: { | ||
debug: apos.asset.options.devicePreviewMode?.debug === true | ||
} | ||
} | ||
: ''; | ||
|
||
return { | ||
module: { | ||
rules: [ | ||
{ | ||
test: /\.css$/, | ||
use: [ | ||
'vue-style-loader', | ||
// https://github.com/vuejs/vue-style-loader/issues/46#issuecomment-670624576 | ||
{ | ||
loader: 'css-loader', | ||
options: { | ||
esModule: false, | ||
sourceMap: true | ||
} | ||
} | ||
mediaToContainerQueriesLoader, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
'css-loader' | ||
] | ||
}, | ||
// https://github.com/vuejs/vue-style-loader/issues/46#issuecomment-670624576 | ||
{ | ||
test: /\.s[ac]ss$/, | ||
use: [ | ||
'vue-style-loader', | ||
{ | ||
loader: 'css-loader', | ||
options: { | ||
esModule: false, | ||
sourceMap: true | ||
} | ||
}, | ||
mediaToContainerQueriesLoader, | ||
'css-loader', | ||
{ | ||
loader: 'postcss-loader', | ||
options: { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
module.exports = function (source) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The road to hell is paved with good intentions |
||
const schema = { | ||
title: 'Media to Container Queries Loader options', | ||
type: 'object', | ||
properties: { | ||
debug: { | ||
type: 'boolean' | ||
} | ||
} | ||
}; | ||
const options = this.getOptions(schema); | ||
|
||
const mediaQueryRegex = /@media\s*(all|print|screen(?: and | or )?)?([^{]+)[^{]*{([\s\S]*?})\s*(\\n)*}/g; | ||
|
||
const convertToContainerQuery = (mediaFeature, content) => { | ||
// NOTE: container query does not work with the combo | ||
// - min-width, max-width, min-height, max-height | ||
// - lower than, lower than equal, greater than, greater than equal | ||
const DESCRIPTORS = [ | ||
'min-width', | ||
'max-width', | ||
'min-height', | ||
'max-height' | ||
]; | ||
const OPERATORS = [ | ||
'>', | ||
'>=', | ||
'<', | ||
'<=' | ||
]; | ||
|
||
if ( | ||
options.debug && | ||
DESCRIPTORS.some(descriptor => mediaFeature.includes(descriptor)) && | ||
OPERATORS.some(operator => mediaFeature.includes(operator)) | ||
) { | ||
console.warn('[mediaToContainerQueryLoader] Unsupported media query', mediaFeature); | ||
} | ||
|
||
return `@container ${mediaFeature} {${content}}`; | ||
}; | ||
|
||
// Prepend container query to media queries | ||
const modifiedSource = source.replace(mediaQueryRegex, (match, mediaType, mediaFeature, content) => { | ||
if ( | ||
mediaType === 'print' || | ||
( | ||
mediaType !== undefined && | ||
([ 'all', 'screen' ].some(media => mediaType.includes(media))) === false | ||
) | ||
) { | ||
return match; | ||
} | ||
|
||
const containerQuery = convertToContainerQuery(mediaFeature, content); | ||
|
||
return `${containerQuery} ${match}`; | ||
}); | ||
|
||
return modifiedSource; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems more UX-friendly to me