From 660cb015821be3ae5a67a4a7127741aacecfde40 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 28 Aug 2026 16:50:41 -0400 Subject: [PATCH 1/4] Resolve field widths to column spans Move the width table out of publish.css into its own stylesheet, and let it accept spans of 1-12 alongside the existing percentages and size keywords. Every value maps to a span of the same twelve column grid the publish form already uses. The percentages were always exact twelfths, so nothing that renders today changes. The rules are now unconditional, with the container query that collapses everything to a single column moved onto the grid itself. That leaves each grid free to pick its own collapse point while sharing one width vocabulary. Co-Authored-By: Claude Opus 5 --- resources/css/components/publish.css | 23 ++++++------- resources/css/components/widths.css | 50 ++++++++++++++++++++++++++++ resources/css/cp.css | 1 + 3 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 resources/css/components/widths.css diff --git a/resources/css/components/publish.css b/resources/css/components/publish.css index ef43cf59ad9..8d192521a4f 100644 --- a/resources/css/components/publish.css +++ b/resources/css/components/publish.css @@ -12,7 +12,12 @@ } .form-group { - grid-column: span var(--col-span, var(--grid-columns)); + /* Stacked, until the panel is wide enough for the configured widths to make sense. */ + grid-column: span var(--grid-columns); + + @variant @lg/panel { + grid-column: span var(--col-span, var(--grid-columns)); + } } .spacer-fieldtype { @@ -26,21 +31,15 @@ } } -@variant @lg/panel { - .field-w-25 { --col-span: calc(var(--grid-columns) * 0.25); } - .field-w-33 { --col-span: calc(var(--grid-columns) * 0.333); } - .field-w-50 { --col-span: calc(var(--grid-columns) * 0.5); } - .field-w-66 { --col-span: calc(var(--grid-columns) * 0.666); } - .field-w-75 { --col-span: calc(var(--grid-columns) * 0.75); } - .field-w-undefined, .field-w-100 { --col-span: calc(var(--grid-columns) * 1); } -} - - /* GROUP PUBLISH COMPONENT / FIELD GRID (e.g. BLUEPRINT) =================================================== */ .field-grid { > * { - grid-column: span var(--col-span, var(--grid-columns)); + grid-column: span var(--grid-columns); + + @variant @lg/panel { + grid-column: span var(--col-span, var(--grid-columns)); + } } } diff --git a/resources/css/components/widths.css b/resources/css/components/widths.css new file mode 100644 index 00000000000..e1e579c804b --- /dev/null +++ b/resources/css/components/widths.css @@ -0,0 +1,50 @@ +/* GROUP WIDTHS +=================================================== */ + +/* + * A configured width resolves to a span of a twelve column grid. + * + * Consumed by the publish form grid (publish.css) and the dashboard widget grid + * (widgets.css). Each of those decides for itself at what container width it + * stops honouring the span and stacks everything instead, so these rules are + * unconditional. + * + * Spans and percentages are shared. The size keywords are widgets only, and are + * deliberately not given .field-w- equivalents. + * + * Spans are written as a fraction of --grid-columns rather than as plain numbers + * so that a grid redefining it continues to opt out of column spanning entirely. + * The asset editor does this by setting it to `none`. + */ + +.field-w-1, .widget-w-1 { --col-span: calc(var(--grid-columns) * 1 / 12); } +.field-w-2, .widget-w-2 { --col-span: calc(var(--grid-columns) * 2 / 12); } +.field-w-3, .widget-w-3 { --col-span: calc(var(--grid-columns) * 3 / 12); } +.field-w-4, .widget-w-4 { --col-span: calc(var(--grid-columns) * 4 / 12); } +.field-w-5, .widget-w-5 { --col-span: calc(var(--grid-columns) * 5 / 12); } +.field-w-6, .widget-w-6 { --col-span: calc(var(--grid-columns) * 6 / 12); } +.field-w-7, .widget-w-7 { --col-span: calc(var(--grid-columns) * 7 / 12); } +.field-w-8, .widget-w-8 { --col-span: calc(var(--grid-columns) * 8 / 12); } +.field-w-9, .widget-w-9 { --col-span: calc(var(--grid-columns) * 9 / 12); } +.field-w-10, .widget-w-10 { --col-span: calc(var(--grid-columns) * 10 / 12); } +.field-w-11, .widget-w-11 { --col-span: calc(var(--grid-columns) * 11 / 12); } +.field-w-12, .widget-w-12 { --col-span: calc(var(--grid-columns) * 12 / 12); } + +/* Percentages. The widths available before spans, and still what the width + selector writes. They were always exact twelfths. */ +.field-w-25, .widget-w-25 { --col-span: calc(var(--grid-columns) * 3 / 12); } +.field-w-33, .widget-w-33 { --col-span: calc(var(--grid-columns) * 4 / 12); } +.field-w-50, .widget-w-50 { --col-span: calc(var(--grid-columns) * 6 / 12); } +.field-w-66, .widget-w-66 { --col-span: calc(var(--grid-columns) * 8 / 12); } +.field-w-75, .widget-w-75 { --col-span: calc(var(--grid-columns) * 9 / 12); } +.field-w-100, .widget-w-100 { --col-span: calc(var(--grid-columns) * 12 / 12); } + +/* Size keywords. Widgets only. Fields have never accepted these. */ +.widget-w-sm { --col-span: calc(var(--grid-columns) * 4 / 12); } +.widget-w-md { --col-span: calc(var(--grid-columns) * 6 / 12); } +.widget-w-lg { --col-span: calc(var(--grid-columns) * 9 / 12); } +.widget-w-full { --col-span: calc(var(--grid-columns) * 12 / 12); } + +/* No width configured. Needs a rule of its own rather than relying on the + var() fallback, because --col-span inherits. */ +.field-w-undefined, .widget-w-undefined { --col-span: calc(var(--grid-columns) * 12 / 12); } diff --git a/resources/css/cp.css b/resources/css/cp.css index dd8ab0f1172..3a612b9d78c 100644 --- a/resources/css/cp.css +++ b/resources/css/cp.css @@ -26,6 +26,7 @@ @import './components/stacks.css'; @import './components/tabs.css'; @import './components/tooltips.css'; +@import './components/widths.css'; @import './components/wizard.css'; /* Fieldtypes */ From 45be763aa2e52eed48ca15f11baadaf3d24f8e73 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 28 Aug 2026 16:51:03 -0400 Subject: [PATCH 2/4] Lay dashboard widgets out on the shared column grid Widgets each had their own four step responsive ramp, so two widgets set to 50 rendered a third wide at large viewports and never sat side by side. Replace the ramp with the same twelve column grid the publish form uses, and one container query for the whole dashboard. The threshold stays at 672px, where widgets already went from full to half width, so the point at which they first pair up is unchanged. Co-Authored-By: Claude Opus 5 --- resources/css/components/widgets.css | 22 +++++++++++++++++++++ resources/css/cp.css | 1 + resources/js/pages/Dashboard.vue | 29 +++------------------------- 3 files changed, 26 insertions(+), 26 deletions(-) create mode 100644 resources/css/components/widgets.css diff --git a/resources/css/components/widgets.css b/resources/css/components/widgets.css new file mode 100644 index 00000000000..1705d3b61b6 --- /dev/null +++ b/resources/css/components/widgets.css @@ -0,0 +1,22 @@ +/* GROUP DASHBOARD WIDGETS +=================================================== */ + +.widgets { + display: grid; + --grid-columns: 12; + grid-template-columns: repeat(var(--grid-columns), 1fr); + @apply gap-6; + + > * { + /* + * Stacked, until the dashboard is wide enough for the arrangement to + * hold up. One threshold for every widget, so there are only ever two + * layouts: the one that was configured, or everything in order. + */ + grid-column: span var(--grid-columns); + + @variant @2xl/widgets { + grid-column: span var(--col-span, var(--grid-columns)); + } + } +} diff --git a/resources/css/cp.css b/resources/css/cp.css index 3a612b9d78c..16a1d333059 100644 --- a/resources/css/cp.css +++ b/resources/css/cp.css @@ -26,6 +26,7 @@ @import './components/stacks.css'; @import './components/tabs.css'; @import './components/tooltips.css'; +@import './components/widgets.css'; @import './components/widths.css'; @import './components/wizard.css'; diff --git a/resources/js/pages/Dashboard.vue b/resources/js/pages/Dashboard.vue index 57d1d9cb1ab..fba3bc200f7 100644 --- a/resources/js/pages/Dashboard.vue +++ b/resources/js/pages/Dashboard.vue @@ -15,30 +15,7 @@ const props = defineProps({ if (props.widgets.length === 0) useArchitecturalBackground(); function classes(widget) { - return `${widget.classes} ${tailwindWidthClass(widget.width)}`; -} - -function tailwindWidthClass(width) { - const sizes = { - sm: 'w-full @2xl:w-1/2 @4xl:w-1/3 @7xl:w-1/4', - md: 'w-full @2xl:w-1/2 @4xl:w-1/2 @7xl:w-1/3', - lg: 'w-full @2xl:w-full @4xl:w-2/3 @7xl:w-3/4', - full: 'w-full', - }; - - // For backward compatibility, map old numeric widths to new sizes - const legacyMap = { - 25: 'sm', - 33: 'sm', - 50: 'md', - 66: 'md', - 75: 'lg', - 100: 'full' - }; - - const size = typeof width === 'number' ? (legacyMap[width] ?? 'full') : width; - - return sizes[size] ?? sizes.md; + return `${widget.classes} widget-w-${widget.width}`; } @@ -48,10 +25,10 @@ function tailwindWidthClass(width) {