Skip to content

Commit

Permalink
[5.x] Field actions (#10352)
Browse files Browse the repository at this point in the history
Co-authored-by: Jason Varga <jason@pixelfear.com>
  • Loading branch information
jacksleight and jasonvarga authored Nov 20, 2024
1 parent a272414 commit 63be821
Show file tree
Hide file tree
Showing 34 changed files with 681 additions and 153 deletions.
2 changes: 1 addition & 1 deletion resources/css/components/fieldtypes/bard.css
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@
========================================================================== */

.bard-fullscreen {
@apply fixed bg-gray-200 dark:bg-dark-700 inset-0 min-h-screen overflow-scroll rounded-none pt-16;
@apply fixed bg-gray-200 dark:bg-dark-700 inset-0 min-h-screen overflow-scroll rounded-none pt-14;

& > .bard-editor {
@apply bg-white dark:bg-dark-800 shadow dark:shadow-dark max-w-xl mx-auto rounded relative my-6 px-8;
Expand Down
5 changes: 3 additions & 2 deletions resources/css/components/fieldtypes/code.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@
}
}

.code-fieldtype-toolbar select {
.code-fieldtype-toolbar select,
.code-fieldtype-toolbar-fullscreen select {
@apply h-8 text-sm leading-none;
}


.code-fieldtype-container.code-fullscreen {
@apply fixed bg-gray-200 dark:bg-dark-600 inset-0 min-h-screen overflow-scroll rounded-none pt-12;
@apply fixed bg-gray-200 dark:bg-dark-600 inset-0 min-h-screen overflow-scroll rounded-none pt-14;

.code-fieldtype-toolbar {
@apply fixed z-2 top-0 w-full px-6 py-2 h-12 shadow dark:shadow-dark text-base rounded-none bg-gradient-to-b from-white to-gray-100 dark:from-dark-550 dark:to-dark-600;
Expand Down
5 changes: 2 additions & 3 deletions resources/css/components/fieldtypes/markdown.css
Original file line number Diff line number Diff line change
Expand Up @@ -212,13 +212,12 @@
}

.markdown-fieldtype-wrapper.markdown-fullscreen {
@apply fixed bg-gray-200 inset-0 min-h-screen overflow-scroll rounded-none pt-16;
@apply fixed bg-gray-200 inset-0 min-h-screen overflow-scroll rounded-none pt-14 border-0;
/* padding-top: 52px; offset the nav */
will-change: transform;

.markdown-toolbar {
@apply fixed top-0 w-full px-6 py-2 h-12 shadow text-base flex items-center justify-between;
@apply bg-gradient-to-b from-white to-gray-100;
@apply border-0;
}

.mode-wrap {
Expand Down
9 changes: 9 additions & 0 deletions resources/css/components/publish.css
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ code.parent-url {
@apply absolute inset-0 cursor-not-allowed bg-white/25;
z-index: 100;
}

.has-field-dropdown {
.field-inner {
@apply pr-6;
}
.field-dropdown {
@apply block absolute bottom-0 right-0;
}
}
}

.publish-field {
Expand Down
13 changes: 13 additions & 0 deletions resources/css/elements/buttons.css
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,19 @@ td .btn-icon {
}
}

.btn-quick-action {
@apply text-center p-0 text-xl text-gray-600 dark:text-dark-150 rounded-full w-8 h-8 outline-none shrink-0 leading-none inline-flex items-center justify-center;

&:hover {
@apply bg-gray-400 dark:bg-dark-700;
}

&:active, &:focus:hover {
outline: none;
@apply bg-gray-500 dark:bg-dark-750;
}
}

.super-btn {
@apply p-4 flex items-start hover:bg-gray-200 border border-transparent rounded-md space-x-4 rtl:space-x-reverse;
@apply dark:hover:bg-dark-575 dark:hover:border-dark-400;
Expand Down
40 changes: 40 additions & 0 deletions resources/css/elements/dropdowns.css
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,44 @@
.align-left & {
right: auto ; [dir="rtl"] & { right: auto ; left: auto ; }
}

}

.quick-list {

@apply relative;

.quick-list-content {
@apply absolute top-1/2 right-full mr-[8px] -translate-y-1/2 min-w-max transition origin-right flex bg-white shadow-quick rounded-full p-0.5;
&::before {
content: '';
position: absolute;
inset: -8px -8px -8px -20px;
z-index: -1;
}
button, a {
@apply rounded-full p-1.5 text-gray-800 flex shrink-0;
&:hover {
@apply bg-blue text-white;
}
}
&:empty {
@apply hidden;
}
}

button.warning, a.warning {
@apply text-red-500;

&:hover {
@apply bg-red-500 text-white;
}
}

&:not(:hover) {
.quick-list-content {
@apply opacity-0 pointer-events-none scale-[0.85];
}
}

}
2 changes: 1 addition & 1 deletion resources/css/vendors/codemirror.css
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@


.CodeMirror-fullscreen {
@apply fixed h-auto inset-0 top-12 rounded-none;
@apply fixed h-auto inset-0 pt-14 rounded-none;
}

.CodeMirror-rulers {
Expand Down
1 change: 1 addition & 0 deletions resources/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Vue.prototype.$echo = Statamic.$echo;
Vue.prototype.$bard = Statamic.$bard;
Vue.prototype.$keys = Statamic.$keys;
Vue.prototype.$reveal = Statamic.$reveal;
Vue.prototype.$fieldActions = Statamic.$fieldActions;
Vue.prototype.$slug = Statamic.$slug;

import Moment from 'moment';
Expand Down
4 changes: 4 additions & 0 deletions resources/js/bootstrap/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import PublishForm from '../components/publish/PublishForm.vue';
import Fields from '../components/publish/Fields.vue';
import FieldsContainer from '../components/publish/FieldsContainer.vue'; // deprecated
import Field from '../components/publish/Field.vue';
import FullscreenHeader from '../components/publish/FullscreenHeader.vue';
import FieldMeta from '../components/publish/FieldMeta.vue';
import FieldActions from '../components/field-actions/FieldActions.vue';
import ConfigureTabs from '../components/configure/Tabs.vue';
import PublishTabs from '../components/publish/Tabs.vue';
import PublishSections from '../components/publish/Sections.vue';
Expand Down Expand Up @@ -77,6 +79,8 @@ Vue.component('publish-fields', Fields);
Vue.component('publish-fields-container', FieldsContainer);
Vue.component('publish-field', Field);
Vue.component('publish-field-meta', FieldMeta);
Vue.component('publish-field-actions', FieldActions);
Vue.component('publish-field-fullscreen-header', FullscreenHeader);
Vue.component('configure-tabs', ConfigureTabs);
Vue.component('publish-tabs', PublishTabs);
Vue.component('publish-sections', PublishSections);
Expand Down
10 changes: 7 additions & 3 deletions resources/js/components/DropdownList.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<template>
<popover class="dropdown-list" :disabled="disabled" :placement="placement" :autoclose="autoclose" @opened="$emit('opened')" @closed="$emit('closed')">
<popover class="dropdown-list" :disabled="disabled" :placement="placement" :autoclose="autoclose" :offset="offset" @opened="$emit('opened')" @closed="$emit('closed')">
<template #trigger>
<slot name="trigger">
<button class="rotating-dots-button" :aria-label="__('Open Dropdown')">
<button class="rotating-dots-button" :aria-label="__('Open Dropdown')" type="button">
<svg class="rotating-dots fill-current" width="12" viewBox="0 0 24 24"><circle cx="3" cy="12" r="3"/><circle cx="12" cy="12" r="3"/><circle cx="21" cy="12" r="3"/></svg>
</button>
</slot>
Expand All @@ -25,7 +25,11 @@ export default {
autoclose: {
type: Boolean,
default: false
}
},
offset: {
type: Array,
default: () => [10, 0]
},
},
computed: {
strategy() {
Expand Down
6 changes: 6 additions & 0 deletions resources/js/components/Statamic.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Echo from './Echo';
import Bard from './Bard';
import Keys from './keys/Keys';
import Hooks from './Hooks';
import FieldActions from './field-actions/FieldActions';
import Reveal from './Reveal';
import Components from './Components';
import FieldConditions from './FieldConditions';
Expand All @@ -12,6 +13,7 @@ const echo = new Echo;
const bard = new Bard;
const keys = new Keys;
const hooks = new Hooks;
const fieldActions = new FieldActions;
const reveal = new Reveal;
const components = new Components;
const conditions = new FieldConditions;
Expand Down Expand Up @@ -54,6 +56,10 @@ export default new Vue({
return hooks;
},

$fieldActions() {
return fieldActions;
},

$reveal() {
return reveal;
},
Expand Down
28 changes: 28 additions & 0 deletions resources/js/components/field-actions/DropdownActions.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<template>
<div>
<button
v-for="action in actions"
@click="run(action)"
v-text="action.title" />
</div>
</template>

<script>
export default {
props: ['actions'],
inject: ['popover'],
methods: {
run(action) {
action.run();
this.popover.vm.close();
},
}
}
</script>
38 changes: 38 additions & 0 deletions resources/js/components/field-actions/FieldAction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export default class FieldAction {
#payload;
#run;
#visible;
#visibleWhenReadOnly;
#icon;
#quick;

constructor(action, payload) {
this.#payload = payload;
this.#run = action.run;
this.#visible = action.visible ?? true;
this.#visibleWhenReadOnly = action.visibleWhenReadOnly ?? false;
this.#icon = action.icon ?? 'image';
this.#quick = action.quick ?? false;
this.title = action.title;
}

get visible() {
if (this.#payload.isReadOnly && !this.#visibleWhenReadOnly) {
return false;
}

return typeof this.#visible === 'function' ? this.#visible(this.#payload) : this.#visible;
}

get quick() {
return typeof this.#quick === 'function' ? this.#quick(this.#payload) : this.#quick;
}

get icon() {
return typeof this.#icon === 'function' ? this.#icon(this.#payload) : this.#icon;
}

run() {
this.#run(this.#payload);
}
}
21 changes: 21 additions & 0 deletions resources/js/components/field-actions/FieldActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import uid from 'uniqid';

class FieldActions {
constructor() {
this.actions = {};
}

add(name, action) {
if (this.actions[name] === undefined) {
this.actions[name] = [];
}

this.actions[name].push(action);
}

get(name) {
return this.actions[name] || [];
}
}

export default FieldActions;
36 changes: 36 additions & 0 deletions resources/js/components/field-actions/FieldActions.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<template>
<div class="field-dropdown">
<div class="quick-list">
<div class="quick-list-content">
<a
v-for="(action, index) in actions.filter(a => a.quick)"
:key="index"
@click="action.run()"
v-tooltip="action.title"
>
<svg-icon :name="action.icon" class="h-3 w-3" />
</a>
</div>
<dropdown-list placement="left-start" :offset="[7, -3]">
<dropdown-actions :actions="actions" />
</dropdown-list>
</div>
</div>
</template>

<script>
import DropdownActions from '../field-actions/DropdownActions.vue';
export default {
components: {
DropdownActions
},
props: {
actions: {
type: Array
}
}
}
</script>
26 changes: 26 additions & 0 deletions resources/js/components/field-actions/HasFieldActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import FieldAction from './FieldAction';

export default {

computed: {

fieldActions() {
return [
...this.$fieldActions.get(this.$options.name),
...this.internalFieldActions
]
.map(action => new FieldAction(action, this.fieldActionPayload))
.filter(action => action.visible);
},

internalFieldActions() {
return [];
},

fieldActionPayload() {
return {};
},

}

}
Loading

0 comments on commit 63be821

Please sign in to comment.