From aa7c82af41cb2bacd732b4d46315ca2030feecc8 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 28 Aug 2026 17:21:24 -0400 Subject: [PATCH 1/3] Add blueprint-based config to widgets Widgets now define a blueprint (width, classes, and any widget-specific fields) instead of relying on ad-hoc config arrays, so their settings can be edited through a standard field UI. --- src/Forms/Widget.php | 69 +++++++++++++++++++++++++++++++++++ src/Widgets/Collection.php | 75 ++++++++++++++++++++++++++++++++++++++ src/Widgets/Template.php | 35 ++++++++++++++++++ src/Widgets/Updater.php | 5 +++ src/Widgets/Widget.php | 66 ++++++++++++++++++++++++--------- 5 files changed, 233 insertions(+), 17 deletions(-) diff --git a/src/Forms/Widget.php b/src/Forms/Widget.php index 4d36333294d..28654db5fa5 100644 --- a/src/Forms/Widget.php +++ b/src/Forms/Widget.php @@ -2,15 +2,84 @@ namespace Statamic\Forms; +use Statamic\Facades\Blueprint; use Statamic\Facades\Form; use Statamic\Facades\User; use Statamic\Widgets\VueComponent; use Statamic\Widgets\Widget as BaseWidget; +use function Statamic\trans as __; + class Widget extends BaseWidget { protected static $handle = 'form'; + public static function description(): ?string + { + return __('statamic::messages.widget_form_description'); + } + + public static function icon(): string + { + return 'forms'; + } + + public function blueprint(): \Statamic\Fields\Blueprint + { + return Blueprint::make()->setContents([ + 'tabs' => [ + 'main' => [ + 'sections' => [ + [ + 'fields' => [ + [ + 'handle' => 'form', + 'field' => [ + 'type' => 'text', + 'display' => __('Form'), + 'instructions' => __('statamic::messages.widget_form_form_instructions'), + 'validate' => 'required', + ], + ], + [ + 'handle' => 'title', + 'field' => [ + 'type' => 'text', + 'display' => __('Title'), + ], + ], + [ + 'handle' => 'limit', + 'field' => [ + 'type' => 'integer', + 'display' => __('Limit'), + 'default' => 5, + ], + ], + [ + 'handle' => 'show_table_header', + 'field' => [ + 'type' => 'toggle', + 'display' => __('Show Table Header'), + 'default' => false, + ], + ], + [ + 'handle' => 'fields', + 'field' => [ + 'type' => 'list', + 'display' => __('Fields'), + ], + ], + ...$this->commonBlueprintFields(), + ], + ], + ], + ], + ], + ]); + } + public function component() { $form = Form::find($handle = $this->config('form')); diff --git a/src/Widgets/Collection.php b/src/Widgets/Collection.php index 2bd922e16bc..7361e3ef571 100644 --- a/src/Widgets/Collection.php +++ b/src/Widgets/Collection.php @@ -4,6 +4,7 @@ use Statamic\Contracts\Entries\Entry as EntryContract; use Statamic\CP\Column; +use Statamic\Facades\Blueprint; use Statamic\Facades\Collection as CollectionAPI; use Statamic\Facades\Scope; use Statamic\Facades\Site; @@ -13,6 +14,80 @@ class Collection extends Widget { + public static function icon(): string + { + return 'collections'; + } + + public function blueprint(): \Statamic\Fields\Blueprint + { + return Blueprint::make()->setContents([ + 'tabs' => [ + 'main' => [ + 'sections' => [ + [ + 'fields' => [ + [ + 'handle' => 'collection', + 'field' => [ + 'type' => 'collections', + 'display' => __('Collection'), + 'max_items' => 1, + 'mode' => 'select', + 'validate' => 'required', + ], + ], + [ + 'handle' => 'title', + 'field' => [ + 'type' => 'text', + 'display' => __('Title'), + 'instructions' => __('statamic::messages.widget_collection_title_instructions'), + ], + ], + [ + 'handle' => 'limit', + 'field' => [ + 'type' => 'integer', + 'display' => __('Limit'), + 'default' => 5, + ], + ], + [ + 'handle' => 'order_by', + 'field' => [ + 'type' => 'text', + 'display' => __('Order By'), + 'instructions' => __('statamic::messages.widget_collection_order_by_instructions'), + 'default' => 'date:desc', + ], + ], + [ + 'handle' => 'show_table_header', + 'field' => [ + 'type' => 'toggle', + 'display' => __('Show Table Header'), + 'default' => false, + ], + ], + [ + 'handle' => 'fields', + 'field' => [ + 'type' => 'list', + 'display' => __('Fields'), + 'instructions' => __('statamic::messages.widget_collection_fields_instructions'), + 'default' => ['title'], + ], + ], + ...$this->commonBlueprintFields(), + ], + ], + ], + ], + ], + ]); + } + public function component() { $collection = $this->config('collection'); diff --git a/src/Widgets/Template.php b/src/Widgets/Template.php index 2a2e53c7ac8..54062c71b0d 100644 --- a/src/Widgets/Template.php +++ b/src/Widgets/Template.php @@ -2,8 +2,43 @@ namespace Statamic\Widgets; +use Statamic\Facades\Blueprint; + +use function Statamic\trans as __; + class Template extends Widget { + public static function icon(): string + { + return 'template-theme-design-layout'; + } + + public function blueprint(): \Statamic\Fields\Blueprint + { + return Blueprint::make()->setContents([ + 'tabs' => [ + 'main' => [ + 'sections' => [ + [ + 'fields' => [ + [ + 'handle' => 'template', + 'field' => [ + 'type' => 'text', + 'display' => __('Template'), + 'instructions' => __('statamic::messages.widget_template_template_instructions'), + 'validate' => 'required', + ], + ], + ...$this->commonBlueprintFields(), + ], + ], + ], + ], + ], + ]); + } + /** * The HTML that should be shown in the widget. * diff --git a/src/Widgets/Updater.php b/src/Widgets/Updater.php index 16d709155ad..f42a0aa18e8 100644 --- a/src/Widgets/Updater.php +++ b/src/Widgets/Updater.php @@ -9,6 +9,11 @@ class Updater extends Widget { + public static function icon(): string + { + return 'updates'; + } + public function component() { if (! User::current()->can('view updates')) { diff --git a/src/Widgets/Widget.php b/src/Widgets/Widget.php index 39c6b21d0b8..bb721439b2c 100644 --- a/src/Widgets/Widget.php +++ b/src/Widgets/Widget.php @@ -6,6 +6,7 @@ use Statamic\Extend\HasHandle; use Statamic\Extend\HasTitle; use Statamic\Extend\RegistersItself; +use Statamic\Facades\Blueprint; use Statamic\Support\Str; abstract class Widget @@ -18,13 +19,6 @@ abstract class Widget protected $config; - /** - * Get config for use within widget. - * - * @param string|null $key - * @param mixed $default - * @return string|\Illuminate\Support\Collection - */ public function config($key = null, $default = null) { if (is_null($key)) { @@ -34,26 +28,64 @@ public function config($key = null, $default = null) return $this->config[$key] ?? $default; } - /** - * Set config when loading widget. - * - * @param array $config - */ public function setConfig($config) { $this->config = $config ?? []; } - /** - * Get container handle. - * - * @return string - */ public static function handle() { return Str::removeRight(static::traitHandle(), '_widget'); } + public static function icon(): string + { + return 'code-block'; + } + + public function blueprint(): \Statamic\Fields\Blueprint + { + return Blueprint::make()->setContents([ + 'tabs' => [ + 'main' => [ + 'sections' => [ + ['fields' => $this->commonBlueprintFields()], + ], + ], + ], + ]); + } + + protected function commonBlueprintFields(): array + { + return [ + [ + 'handle' => 'width', + 'field' => [ + 'type' => 'select', + 'display' => __('Width'), + 'options' => [ + 'sm' => __('Small'), + 'md' => __('Medium'), + 'lg' => __('Large'), + 'full' => __('Full'), + ], + 'default' => 'md', + 'clearable' => false, + 'localizable' => false, + ], + ], + [ + 'handle' => 'classes', + 'field' => [ + 'type' => 'text', + 'display' => __('Classes'), + 'instructions' => __('statamic::messages.widget_classes_instructions'), + ], + ], + ]; + } + public function component() { // From c6a4ddfc8a0f195debdca7bb89ea642404427ef1 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 28 Aug 2026 17:21:32 -0400 Subject: [PATCH 2/3] Add endpoints for saving dashboard widget preferences Introduces DashboardWidgetsController with meta, update, and destroy actions so users can customize which widgets appear on their dashboard and configure them via their blueprint fields. DashboardController now resolves and pre-processes widget configs (from preferences or config fallback) for the editing UI. --- lang/en/messages.php | 10 ++ routes/cp.php | 4 + .../Controllers/CP/DashboardController.php | 50 +++++-- .../CP/DashboardWidgetsController.php | 71 ++++++++++ .../CP/DashboardWidgetsControllerTest.php | 129 ++++++++++++++++++ 5 files changed, 256 insertions(+), 8 deletions(-) create mode 100644 src/Http/Controllers/CP/DashboardWidgetsController.php create mode 100644 tests/Feature/CP/DashboardWidgetsControllerTest.php diff --git a/lang/en/messages.php b/lang/en/messages.php index 5899880f2c1..0b5fd23e2c9 100644 --- a/lang/en/messages.php +++ b/lang/en/messages.php @@ -305,5 +305,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. date:desc or title:asc.', + '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', ]; diff --git a/routes/cp.php b/routes/cp.php index 2626fc0f7f2..f1cf37964e0 100644 --- a/routes/cp.php +++ b/routes/cp.php @@ -49,6 +49,7 @@ use Statamic\Http\Controllers\CP\CommandPaletteController; use Statamic\Http\Controllers\CP\CpController; use Statamic\Http\Controllers\CP\DashboardController; +use Statamic\Http\Controllers\CP\DashboardWidgetsController; use Statamic\Http\Controllers\CP\DuplicatesController; use Statamic\Http\Controllers\CP\FieldActionModalController; use Statamic\Http\Controllers\CP\Fields\AdditionalBlueprintController; @@ -161,6 +162,9 @@ Route::get('/', StartPageController::class)->name('index'); Route::get('dashboard', [DashboardController::class, 'index'])->name('dashboard'); + Route::get('dashboard/widgets/meta', [DashboardWidgetsController::class, 'meta'])->name('dashboard.widgets.meta'); + Route::patch('dashboard/widgets', [DashboardWidgetsController::class, 'update'])->name('dashboard.widgets.update'); + Route::delete('dashboard/widgets', [DashboardWidgetsController::class, 'destroy'])->name('dashboard.widgets.destroy'); Route::get('select-site/{handle}', [SelectSiteController::class, 'select']); diff --git a/src/Http/Controllers/CP/DashboardController.php b/src/Http/Controllers/CP/DashboardController.php index be23e7b0057..da90eabec5e 100644 --- a/src/Http/Controllers/CP/DashboardController.php +++ b/src/Http/Controllers/CP/DashboardController.php @@ -19,10 +19,17 @@ class DashboardController extends CpController */ public function index(Loader $loader) { - $widgets = $this->getDisplayableWidgets($loader); + $configs = $this->resolvedWidgetConfigs(); + $widgets = $this->buildDisplayableWidgets($loader, $configs); return Inertia::render('Dashboard', [ 'widgets' => $widgets, + 'widgetConfigs' => $this->preProcessedConfigs($configs), + 'hasCustomWidgets' => Preference::get('dashboard.widgets') !== null, + 'canEditWidgets' => User::current()->can('access cp'), + 'widgetMetaUrl' => cp_route('dashboard.widgets.meta'), + 'widgetUpdateUrl' => cp_route('dashboard.widgets.update'), + 'widgetDestroyUrl' => cp_route('dashboard.widgets.destroy'), 'pro' => Statamic::pro(), 'blueprintsUrl' => cp_route('blueprints.index'), 'collectionsCreateUrl' => cp_route('collections.create'), @@ -30,20 +37,47 @@ public function index(Loader $loader) ]); } + private function resolvedWidgetConfigs() + { + return Preference::get('dashboard.widgets') ?? config('statamic.cp.widgets') ?? []; + } + + private function normalizedConfigs($widgets) + { + return collect($widgets)->map(function ($config) { + return is_string($config) ? ['type' => $config] : $config; + }); + } + + private function preProcessedConfigs($configs) + { + $availableWidgets = collect(app('statamic.widgets')->all()); + + return $this->normalizedConfigs($configs)->map(function ($config) use ($availableWidgets) { + $handle = $config['type']; + $widgetClass = $availableWidgets->get($handle); + + if (! $widgetClass) { + return $config; + } + + $widget = app($widgetClass); + $widget->setConfig($config); + $fields = $widget->blueprint()->fields()->addValues($config)->preProcess(); + + return array_merge(['type' => $handle], $fields->values()->all()); + })->values()->all(); + } + /** * Get displayable widgets. * * @param Loader $loader * @return \Illuminate\Support\Collection */ - private function getDisplayableWidgets($loader) + private function buildDisplayableWidgets($loader, $widgets) { - $widgets = Preference::get('widgets') ?? config('statamic.cp.widgets') ?? []; - - return collect($widgets) - ->map(function ($config) { - return is_string($config) ? ['type' => $config] : $config; - }) + return $this->normalizedConfigs($widgets) ->filter(function ($config) { if ($config['type'] === 'getting_started') { return false; diff --git a/src/Http/Controllers/CP/DashboardWidgetsController.php b/src/Http/Controllers/CP/DashboardWidgetsController.php new file mode 100644 index 00000000000..03621801f64 --- /dev/null +++ b/src/Http/Controllers/CP/DashboardWidgetsController.php @@ -0,0 +1,71 @@ +authorize('access cp'); + + return $this->availableWidgets()->map(function ($class, $handle) { + /** @var Widget $widget */ + $widget = app($class); + $widget->setConfig(['type' => $handle]); + + $blueprint = $widget->blueprint(); + $fields = $blueprint->fields()->addValues([])->preProcess(); + + return [ + 'handle' => $handle, + 'title' => $class::title(), + 'icon' => $class::icon(), + 'blueprint' => $blueprint->toPublishArray(), + 'meta' => $fields->meta(), + 'defaults' => $fields->values()->all(), + ]; + })->values()->all(); + } + + public function update(Request $request) + { + $this->authorize('access cp'); + + $widgets = collect($request->input('widgets', [])) + ->map(fn ($config) => is_string($config) ? ['type' => $config] : (array) $config) + ->filter(fn ($config) => isset($config['type']) && $this->availableWidgets()->has($config['type'])) + ->map(function ($config) { + $handle = $config['type']; + $widgetClass = $this->availableWidgets()->get($handle); + $widget = app($widgetClass); + $widget->setConfig(['type' => $handle]); + $processedValues = $widget->blueprint()->fields()->addValues($config)->process()->values()->all(); + + return array_merge($config, $processedValues); + }) + ->values() + ->all(); + + User::current()->setPreference('dashboard.widgets', $widgets)->save(); + + return response()->noContent(); + } + + public function destroy() + { + $this->authorize('access cp'); + + User::current()->removePreference('dashboard.widgets')->save(); + + return response()->noContent(); + } + + private function availableWidgets() + { + return collect(app('statamic.widgets')->all()); + } +} diff --git a/tests/Feature/CP/DashboardWidgetsControllerTest.php b/tests/Feature/CP/DashboardWidgetsControllerTest.php new file mode 100644 index 00000000000..45c3fe61a36 --- /dev/null +++ b/tests/Feature/CP/DashboardWidgetsControllerTest.php @@ -0,0 +1,129 @@ +makeSuper()->save(); + + $response = $this->actingAs($user) + ->getJson(cp_route('dashboard.widgets.meta')) + ->assertOk() + ->json(); + + $handles = collect($response)->pluck('handle')->all(); + + $this->assertContains('collection', $handles); + $this->assertContains('updater', $handles); + $this->assertContains('template', $handles); + + $collection = collect($response)->firstWhere('handle', 'collection'); + $this->assertArrayHasKey('blueprint', $collection); + $this->assertArrayHasKey('meta', $collection); + $this->assertArrayHasKey('defaults', $collection); + $this->assertArrayHasKey('icon', $collection); + $this->assertArrayHasKey('title', $collection); + } + + #[Test] + public function it_requires_access_cp_for_meta() + { + $this->getJson(cp_route('dashboard.widgets.meta'))->assertUnauthorized(); + } + + #[Test] + public function it_saves_widgets_to_user_preference() + { + $user = User::make()->makeSuper()->save(); + + $payload = [ + 'widgets' => [ + ['type' => 'collection', 'collection' => 'pages', 'limit' => 5], + ['type' => 'updater'], + ], + ]; + + $this->actingAs($user) + ->patchJson(cp_route('dashboard.widgets.update'), $payload) + ->assertNoContent(); + + $fresh = $user->fresh(); + $saved = $fresh->getPreference('dashboard.widgets'); + + $this->assertIsArray($saved); + $this->assertCount(2, $saved); + $this->assertEquals('collection', $saved[0]['type']); + $this->assertEquals('updater', $saved[1]['type']); + + $this->assertEquals($saved, $fresh->preferences()['dashboard']['widgets']); + } + + #[Test] + public function it_processes_fieldtype_values_when_saving() + { + // The collections fieldtype (max_items: 1) sends an array from the frontend. + // It should be processed to a string before being stored. + $user = User::make()->makeSuper()->save(); + + $this->actingAs($user) + ->patchJson(cp_route('dashboard.widgets.update'), [ + 'widgets' => [ + ['type' => 'collection', 'collection' => ['pages']], + ], + ]) + ->assertNoContent(); + + $saved = $user->fresh()->getPreference('dashboard.widgets'); + + $this->assertEquals('pages', $saved[0]['collection']); + } + + #[Test] + public function it_filters_out_unknown_widget_types() + { + $user = User::make()->makeSuper()->save(); + + $this->actingAs($user) + ->patchJson(cp_route('dashboard.widgets.update'), [ + 'widgets' => [ + ['type' => 'collection'], + ['type' => 'i_do_not_exist'], + ], + ]) + ->assertNoContent(); + + $saved = $user->fresh()->getPreference('dashboard.widgets'); + + $this->assertCount(1, $saved); + $this->assertEquals('collection', $saved[0]['type']); + } + + #[Test] + public function it_removes_widget_preference() + { + $user = User::make()->makeSuper()->preferences([ + 'dashboard' => ['widgets' => [['type' => 'collection']]], + ])->save(); + + $this->assertNotNull($user->getPreference('dashboard.widgets')); + + $this->actingAs($user) + ->deleteJson(cp_route('dashboard.widgets.destroy')) + ->assertNoContent(); + + $fresh = $user->fresh(); + + $this->assertNull($fresh->getPreference('dashboard.widgets')); + $this->assertArrayNotHasKey('dashboard', $fresh->preferences()); + } +} From 7245d80676745024b83f7821e7fe917b5581e2f5 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 28 Aug 2026 17:21:39 -0400 Subject: [PATCH 3/3] Add dashboard widget editing UI Adds WidgetPicker, WidgetEditChrome, and WidgetConfigStack components so widgets can be added, reordered, resized, and configured directly from the dashboard, plus a WidthSelector field and supporting styles. --- resources/css/components/dashboard.css | 40 +++ resources/css/cp.css | 1 + .../dashboard/WidgetConfigStack.vue | 54 ++++ .../components/dashboard/WidgetEditChrome.vue | 64 +++++ .../js/components/dashboard/WidgetPicker.vue | 26 ++ .../js/components/fields/WidthSelector.vue | 3 +- resources/js/components/ui/Widget.vue | 1 - resources/js/pages/Dashboard.vue | 230 +++++++++++++++++- 8 files changed, 404 insertions(+), 15 deletions(-) create mode 100644 resources/css/components/dashboard.css create mode 100644 resources/js/components/dashboard/WidgetConfigStack.vue create mode 100644 resources/js/components/dashboard/WidgetEditChrome.vue create mode 100644 resources/js/components/dashboard/WidgetPicker.vue diff --git a/resources/css/components/dashboard.css b/resources/css/components/dashboard.css new file mode 100644 index 00000000000..7323634b90b --- /dev/null +++ b/resources/css/components/dashboard.css @@ -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; +} diff --git a/resources/css/cp.css b/resources/css/cp.css index 16a1d333059..174ea8abf44 100644 --- a/resources/css/cp.css +++ b/resources/css/cp.css @@ -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'; diff --git a/resources/js/components/dashboard/WidgetConfigStack.vue b/resources/js/components/dashboard/WidgetConfigStack.vue new file mode 100644 index 00000000000..3b664b187c2 --- /dev/null +++ b/resources/js/components/dashboard/WidgetConfigStack.vue @@ -0,0 +1,54 @@ + + + diff --git a/resources/js/components/dashboard/WidgetEditChrome.vue b/resources/js/components/dashboard/WidgetEditChrome.vue new file mode 100644 index 00000000000..8fe7f7b5375 --- /dev/null +++ b/resources/js/components/dashboard/WidgetEditChrome.vue @@ -0,0 +1,64 @@ + + + diff --git a/resources/js/components/dashboard/WidgetPicker.vue b/resources/js/components/dashboard/WidgetPicker.vue new file mode 100644 index 00000000000..28618f8d5a3 --- /dev/null +++ b/resources/js/components/dashboard/WidgetPicker.vue @@ -0,0 +1,26 @@ + + + diff --git a/resources/js/components/fields/WidthSelector.vue b/resources/js/components/fields/WidthSelector.vue index 2eab1c8d149..31dedf26bd3 100644 --- a/resources/js/components/fields/WidthSelector.vue +++ b/resources/js/components/fields/WidthSelector.vue @@ -33,10 +33,11 @@ const selected = computed(() => { const selectedSpan = computed(() => widthToColumnSpan(selected.value)) 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', }, }, diff --git a/resources/js/components/ui/Widget.vue b/resources/js/components/ui/Widget.vue index f4c9e791ed3..9665e5f104c 100644 --- a/resources/js/components/ui/Widget.vue +++ b/resources/js/components/ui/Widget.vue @@ -11,7 +11,6 @@ const props = defineProps({ const slots = useSlots(); const hasHeader = computed(() => Boolean(props.title || props.icon || slots.actions)); -