Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions lang/en/messages.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,5 +291,15 @@
'user_wizard_invitation_subject' => 'Activate your new Statamic account on :site',
'user_wizard_roles_groups_intro' => 'Users can be assigned to roles that customize their permissions, access, and abilities throughout the Control Panel.',
'user_wizard_super_admin_instructions' => 'Super admins have complete control and access to everything in the control panel. Grant this role wisely.',
'widget_classes_instructions' => 'Additional CSS classes applied to the widget wrapper.',
'widget_collection_description' => 'Shows recent entries from a collection.',
'widget_collection_fields_instructions' => 'Field handles to display as columns. Defaults to title.',
'widget_collection_order_by_instructions' => 'Field and direction, e.g. <code>date:desc</code> or <code>title:asc</code>.',
'widget_collection_title_instructions' => 'Override the widget title. Defaults to the collection title.',
'widget_form_description' => 'Shows recent submissions for a form.',
'widget_form_form_instructions' => 'The handle of the form to display submissions for.',
'widget_template_description' => 'Renders a custom Blade or Antlers template.',
'widget_template_template_instructions' => 'Path to the template, relative to the views directory.',
'widget_updater_description' => 'Shows available updates for Statamic and your installed addons.',
'width_x_height' => ':width × :height',
];
40 changes: 40 additions & 0 deletions resources/css/components/dashboard.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* ==========================================================================
DASHBOARD WIDGET EDITING
========================================================================== */

.dashboard-widget-inner {
@apply @container/widget relative rounded-xl;
}

/* Hide the chrome on the dragging source — the mirror is what the user sees */
.dashboard-widget-sortable.draggable-source--is-dragging .dashboard-widget-inner {
@apply opacity-50;
}

.dashboard-widget-sortable.draggable-source--is-dragging [data-widget-edit-chrome] {
@apply opacity-0;
}

.dashboard-widget-sortable.draggable-source--is-dragging .dashboard-widget-inner::after {
content: '';
@apply pointer-events-none absolute inset-0 rounded-xl border-2 border-dashed border-ui-accent-bg/40 bg-ui-accent-bg/5 dark:bg-ui-accent-bg/10;
}

/* Mirror — the floating element that follows the cursor while dragging */
body > .draggable-mirror.dashboard-widget-mirror {
@apply rounded-xl shadow-ui-lg ring-1 ring-black/5 dark:ring-white/10;
transition: none !important;
will-change: transform;
}

body > .draggable-mirror.dashboard-widget-mirror [data-widget-edit-chrome] {
@apply bg-white/95 dark:bg-gray-850/95;
}

.dashboard-widget-placeholder {
@apply flex min-h-54 flex-col items-center justify-center gap-3 rounded-xl bg-gray-50 px-6 py-10 text-center ring-1 ring-black/5 dark:bg-gray-900/40 dark:ring-white/10;
}

.dashboard-widget-empty {
@apply flex min-h-64 flex-col items-center justify-center gap-4 rounded-xl bg-gray-50 px-8 py-16 text-center ring-1 ring-black/5 dark:bg-gray-900/30 dark:ring-white/10;
}
1 change: 1 addition & 0 deletions resources/css/cp.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
@import './components/assets.css';
@import './components/blueprints.css';
@import './components/configure.css';
@import './components/dashboard.css';
@import './components/focal-point.css';
@import './components/index-fields.css';
@import './components/notifications.css';
Expand Down
54 changes: 54 additions & 0 deletions resources/js/components/dashboard/WidgetConfigStack.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<script setup>
import { ref, computed, getCurrentInstance } from 'vue';
import { Stack, Button, PublishContainer, PublishFieldsProvider, PublishFields, Icon } from '@/components/ui';

const props = defineProps({
config: { type: Object, required: true },
meta: { type: Object, default: null },
});

const emit = defineEmits(['closed', 'saved']);

const blueprint = computed(() => props.meta?.blueprint);
const title = computed(() => props.meta?.title ?? props.config.type);

function initialValues() {
const defaults = props.meta?.defaults ?? {};
const { type, ...rest } = props.config;
return { ...defaults, ...rest };
}

const values = ref(initialValues());
const fieldMeta = ref(props.meta?.meta || {});
const errors = ref({});
const name = `dashboard-widget-${props.config.type}-${Math.random().toString(36).slice(2, 8)}`;

function save() {
emit('saved', { type: props.config.type, ...values.value });
}
</script>

<template>
<Stack
size="narrow"
open
:title="title"
@update:open="emit('closed')"
>
<PublishContainer
:name="name"
:blueprint="blueprint"
:meta="fieldMeta"
:errors="errors"
v-model="values"
>
<PublishFieldsProvider :fields="blueprint.tabs[0].sections[0].fields">
<PublishFields />
</PublishFieldsProvider>
</PublishContainer>
<template #footer-end>
<Button variant="ghost" :text="__('Cancel')" @click="emit('closed')" />
<Button variant="primary" :text="__('Apply')" @click="save" />
</template>
</Stack>
</template>
62 changes: 62 additions & 0 deletions resources/js/components/dashboard/WidgetEditChrome.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<script setup>
import { Heading, Icon, Button, ButtonGroup } from '@/components/ui';
import WidthSelector from '@/components/fields/WidthSelector.vue';
import { computed } from 'vue';

const props = defineProps({
config: { type: Object, required: true },
meta: { type: Object, default: null },
});

const emit = defineEmits(['configure', 'remove', 'update:width']);

const widthToPercent = { sm: 25, md: 50, lg: 75, full: 100 };
const percentToWidth = { 25: 'sm', 50: 'md', 75: 'lg', 100: 'full' };

const currentWidth = computed(() => widthToPercent[props.config.width ?? 'md']);

function onWidthUpdate(value) {
emit('update:width', percentToWidth[value]);
}
</script>

<template>
<div
data-widget-edit-chrome
class="dashboard-widget-handle absolute inset-0 z-2 flex flex-col cursor-grab items-center justify-center gap-5 rounded-xl bg-white/80 px-4 backdrop-blur-[4px] -m-px border border-dashed border-purple-300 dark:border-purple-300/40 shadow-sm active:cursor-grabbing dark:bg-gray-900/85"
>
<Heading
:text="meta?.title ?? config.type"
:icon="meta?.icon ?? 'code-block'"
/>

<div
class="flex shrink-0 items-center gap-2"
@mousedown.stop
@click.stop
>
<WidthSelector
size="md"
:model-value="currentWidth"
:initial-widths="[25, 50, 75, 100]"
@update:model-value="onWidthUpdate"
/>

<ButtonGroup>
<Button
size="sm"
icon="configure"
:text="__('Configure')"
@click="emit('configure')"
/>
<Button
size="sm"
icon="trash"
icon-only
:aria-label="__('Remove')"
@click="emit('remove')"
/>
</ButtonGroup>
</div>
</div>
</template>
26 changes: 26 additions & 0 deletions resources/js/components/dashboard/WidgetPicker.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<script setup>
import { Dropdown, DropdownMenu, DropdownItem, Button } from '@/components/ui';

const props = defineProps({
widgets: { type: Array, required: true },
});

const emit = defineEmits(['picked']);
</script>

<template>
<Dropdown align="start">
<template #trigger>
<Button :text="__('Add Widget')" icon="plus" />
</template>
<DropdownMenu>
<DropdownItem
v-for="widget in widgets"
:key="widget.handle"
:icon="widget.icon"
:text="widget.title"
@click="emit('picked', widget)"
/>
</DropdownMenu>
</Dropdown>
</template>
3 changes: 2 additions & 1 deletion resources/js/components/fields/WidthSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ const selected = computed(() => {
})

const wrapperClasses = cva({
base: 'relative text-gray-600 dark:text-gray-400 font-mono antialiased bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 with-contrast:border-gray-500 overflow-hidden flex cursor-pointer',
base: 'relative text-gray-600 dark:text-gray-400 font-mono antialiased bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 with-contrast:border-gray-500 overflow-hidden flex cursor-pointer shadow-ui-sm',
variants: {
size: {
base: 'h-6 w-14 text-xs rounded-md',
md: 'h-8 w-16 text-xs rounded-lg',
lg: 'h-10 w-24 text-sm rounded-lg',
},
},
Expand Down
1 change: 0 additions & 1 deletion resources/js/components/ui/Widget.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ const props = defineProps({

const slots = useSlots();
const hasHeader = computed(() => Boolean(props.title || props.icon || slots.actions));

</script>

<template>
Expand Down
Loading
Loading