Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
6b7ff7a
Tree view - start prototype
jaygeorge Jun 8, 2026
5472a6e
Temp unhide Pro
jaygeorge Jun 8, 2026
5ed3e36
Tree view - look through fields
jaygeorge Jun 8, 2026
17f11a9
Merge branch 'forms-2' into forms-2-logic-tree
jaygeorge Jun 8, 2026
0acf072
Merge branch 'forms-2' into forms-2-logic-tree
jaygeorge Jun 8, 2026
849bf5d
Merge branch 'forms-2' into forms-2-logic-tree
jaygeorge Jun 8, 2026
ba47e88
Merge branch 'forms-2' into forms-2-logic-tree
jaygeorge Jun 8, 2026
219e31e
Merge branch 'forms-2' into forms-2-logic-tree
jaygeorge Jun 8, 2026
79ec464
Tree view - break into pages
jaygeorge Jun 8, 2026
b923025
Tree view - connector experiments - demo 1
jaygeorge Jun 8, 2026
1953f71
Put logic tree CSS in separate file
jaygeorge Jun 9, 2026
3ea076b
Remember Logic view preference
jaygeorge Jun 9, 2026
c799c2f
Nest CSS
jaygeorge Jun 9, 2026
837d886
Tree view CSS - tidy and explain everything
jaygeorge Jun 9, 2026
eccae06
Tree view CSS - experiment with radios at the top
jaygeorge Jun 9, 2026
c1027ef
Tree view CSS - remove confusing island class
jaygeorge Jun 9, 2026
7f535a3
Tree view CSS - Pass the destination to the item
jaygeorge Jun 9, 2026
7bb40f2
Tree view CSS - Simplify CSS and remove JS since our paths will alway…
jaygeorge Jun 9, 2026
7042368
Tree view CSS - Simplify by getting rid of inputs
jaygeorge Jun 9, 2026
2631204
Tree view CSS - Simplify by getting rid of inputs
jaygeorge Jun 9, 2026
b6c104c
Tree view CSS - Get multi columns working
jaygeorge Jun 9, 2026
76ef2d4
Tree view CSS - wip
jaygeorge Jun 9, 2026
534239f
Tree view CSS - wip
jaygeorge Jun 9, 2026
7ceacd6
Tree view CSS - use pseudo elements for consistency
jaygeorge Jun 9, 2026
520740f
Tree view CSS - tidy
jaygeorge Jun 9, 2026
ea5c7a1
Tree view CSS - tidy
jaygeorge Jun 9, 2026
f77d74d
Tree view CSS - tidy
jaygeorge Jun 9, 2026
9712a9b
Tree view CSS - get the maths right
jaygeorge Jun 9, 2026
c988836
Tree view CSS - switch to a solid line because of inconsistent dash a…
jaygeorge Jun 9, 2026
8b63443
Tree view CSS - finish tidying
jaygeorge Jun 9, 2026
7ab9c4e
Tree view CSS - make calculations more robust
jaygeorge Jun 9, 2026
922f078
Tree view CSS - loop over fields dynamically
jaygeorge Jun 10, 2026
671e6ba
Tree view CSS - start decorating
jaygeorge Jun 10, 2026
2fb62e2
Tree view - break out of container so we have more space
jaygeorge Jun 10, 2026
cb9d837
Tree view - decorate the page title
jaygeorge Jun 10, 2026
d9d15c2
Tree view - search/replace
jaygeorge Jun 10, 2026
a1e0d17
Tree view - Fix icon location
jaygeorge Jun 10, 2026
2831aff
Tree view - Page name decoration
jaygeorge Jun 10, 2026
f6df5ee
Tree view - pull the page name out of the ul
jaygeorge Jun 10, 2026
b13b168
Tree view - Finish page name decoration
jaygeorge Jun 10, 2026
8642b76
Remove line clamp utility, Tailwind already had it
jaygeorge Jun 10, 2026
9dc5d75
Tree view - Page name decoration
jaygeorge Jun 10, 2026
3468b9b
Tree view - add connector icon
jaygeorge Jun 10, 2026
8c1e967
Tree view - Tidy connector icon
jaygeorge Jun 10, 2026
3732d90
Tree view - Add icons
jaygeorge Jun 10, 2026
70936e1
Tree view - Add density toggle
jaygeorge Jun 10, 2026
6f2e2f9
Tree view - remove unnecessary wrapper
jaygeorge Jun 10, 2026
202f013
Tree view - adjust header to match the height of other tabs
jaygeorge Jun 10, 2026
6d4b3de
Tree view / density toggle - hook up
jaygeorge Jun 10, 2026
9218675
Tree view - tweak gap
jaygeorge Jun 10, 2026
3c693e5
Tree view - add a container to handle overflow for lots of columns
jaygeorge Jun 10, 2026
9566b98
Tree view - add a utility class for breaking out of the .content cont…
jaygeorge Jun 10, 2026
69fd929
Tree view - fix utility class scrollbar position
jaygeorge Jun 10, 2026
ee34a89
Tree view - make view options consistent with Edit tab
jaygeorge Jun 10, 2026
c08e3aa
! Tree view - icons for linked fields
jaygeorge Jun 10, 2026
0187d02
Tree view - style fieldsets
jaygeorge Jun 10, 2026
676017c
Tree view / Fieldsets - pull in the blueprint icons
jaygeorge Jun 10, 2026
76fc304
Tree view / Fieldsets & fields - redesign
jaygeorge Jun 10, 2026
f006e7b
Tree view - fix layout for items with .linked-list__extra-leap-connector
jaygeorge Jun 10, 2026
cd51714
Tree view - remove some overqualified connectors
jaygeorge Jun 10, 2026
6ff7137
Optically align switcher text
jaygeorge Jun 10, 2026
0c1cbbd
Tree view - dark mode pass
jaygeorge Jun 10, 2026
af3410a
Consistent view names
jaygeorge Jun 11, 2026
09d4cc7
Merge branch 'forms-2' into forms-2-logic-tree
jaygeorge Jun 11, 2026
b853ccb
For the compressed view > fieldset, show the field icon
jaygeorge Jun 11, 2026
e8e3172
Merge branch 'forms-2' into forms-2-logic-tree
jaygeorge Jun 11, 2026
eb09526
Compress spacing for fieldsets when the fields are collapsed
jaygeorge Jun 11, 2026
b26e9c5
Remove overqualified selectors
jaygeorge Jun 11, 2026
b51d246
Tree view - add sections
jaygeorge Jun 11, 2026
d41c7d9
Tree view - add first sections
jaygeorge Jun 11, 2026
f9299e9
Tree view / Sections - start changing how we do them
jaygeorge Jun 11, 2026
9256bdc
Revert "Tree view / Sections - start changing how we do them"
jaygeorge Jun 11, 2026
9724a74
Tree view / Sections - change alignment
jaygeorge Jun 11, 2026
9b63845
Tree view / Sections - split out sections
jaygeorge Jun 11, 2026
d1f0d5c
Tree view / Sections - fix gap
jaygeorge Jun 11, 2026
c02dace
Tree view / Sections - start fixing alignment
jaygeorge Jun 11, 2026
8ed532d
Merge branch 'forms-2' into forms-2-logic-tree
jaygeorge Jun 11, 2026
6e6fa36
Tree view / Fieldsets - remove the stacked cards because it knocks of…
jaygeorge Jun 11, 2026
0caa707
Tree view / Fieldsets - restyle fieldsets to keep spacing intact
jaygeorge Jun 11, 2026
b8bb513
Tree view / Fieldsets - restyle fieldsets
jaygeorge Jun 11, 2026
703afcd
Tree view / Fieldsets - restyle fieldset headings
jaygeorge Jun 11, 2026
6ade6f7
Tree view - get rid of unneeded rules
jaygeorge Jun 11, 2026
3bebee6
Number toggle across views
jaygeorge Jun 11, 2026
8309f61
Make number toggle button less floaty
jaygeorge Jun 11, 2026
8b3e4ca
Center buttons
jaygeorge Jun 11, 2026
0952bfe
Revert "Temp unhide Pro"
jaygeorge Jun 11, 2026
1d1eef6
Merge branch 'forms-2' into forms-2-logic-tree
duncanmcclean Jun 15, 2026
d8fd09a
formatting
duncanmcclean Jun 15, 2026
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
5 changes: 5 additions & 0 deletions resources/css/components/forms.css
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@
[data-collapsed-field-icon] {
display: inline-flex;
}

/* Compress spacing for fieldsets when the fields are collapsed */
[data-fieldset-group] {
@apply space-y-5;
}
}


Expand Down
261 changes: 261 additions & 0 deletions resources/css/components/forms/logic-tree.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
/* GROUP LOGIC TREE CONNECTORS
=================================================== */
/*
URL: /cp/forms/{handle}/logic (Tree view)

Connects source fields to destination page headers using CSS anchor
positioning. Inspired by: https://codepen.io/cbolson/pen/emzegWP

Markup (LogicTree.vue):

- Each page column is a .linked-list__column (.linked-list__page-name + .linked-list__sections).
- Each section is a .linked-list__section (.linked-list__section-marker + <ul>).
- Page headers: .linked-list__page-name with anchor-name: --page-N.
- Source fields: .linked-list__connector with --end-connection: --page-N, pointing at the target page’s anchor name.
- Multi-column hops: add .linked-list__page-leap on the source <li> and a .linked-list__extra-leap-connector child for the bridge segment. This helps separate multi-column connectors from adjacent pages.

Direct leap anatomy (source below destination page header):

[destination page header]
┌────── ::after — gap midpoint → destination corner
[source]───────────┘ ::before — source centre → gap midpoint

Each direct connector is two pseudo-elements on .linked-list__connector:

::before — vertical leg from source centre up toward the destination,
plus the left half of the inter-column gap
::after — right half of the gap + rounded corner at the destination

Page leaps (.linked-list__page-leap + .linked-list__extra-leap-connector):

::before on .linked-list__extra-leap-connector — stub out from the
source and along the first gap
::after on .linked-list__extra-leap-connector — corner into the
second column
::before on .linked-list__connector — vertical leg from the buffer
rail down to the destination page header

Anchors:

--start-connection — set on each .linked-list__connector <li> (the source)
var(--end-connection) — anchor name of the target page header, declared in
markup as --end-connection: --page-N on the source <li>
anchor-scope: --start-connection — keeps each source’s connector isolated
*/
@layer components {
.linked-list-container {
/* Don't let max-content grid width expand ancestors — scroll inside here only */
width: 100%;
max-width: 100%;
overflow-x: auto;
overflow-y: visible;
contain: inline-size;
overscroll-behavior-x: contain;
}

.linked-list {
--join-stroke: 1px;
--join-line: var(--join-stroke) solid var(--color-blue-400);
--join-radius: 20px;

:where(.dark) & {
--join-line: var(--join-stroke) solid var(--color-blue-500);
}
--gap: 6rem;
--column-width: 12.5rem;

--item-padding: 1rem;
--item-height: 3.2rem;

/* Bit of a magic number here to pull the line into the gap between the items */
--buffer-top-pull: calc(0.8rem + var(--item-padding));
--buffer-left-pull: 1.5rem;

/* GROUP LOGIC TREE CONNECTORS / LAYOUT
=================================================== */
position: relative;
display: grid;
align-items: start;
grid-auto-flow: column;
grid-auto-columns: var(--column-width);
width: max-content;
margin-inline: auto;
gap: var(--gap);

.linked-list__column {
display: flex;
flex-direction: column;
gap: 1rem;
}

.linked-list__page-name {
@apply px-2 pt-2;
}

.linked-list__sections {
display: flex;
flex-direction: column;
gap: 0.25rem;
}

.linked-list__section {
display: flex;
flex-direction: column;
}

.linked-list__section-marker {
display: flex;
align-items: end;
justify-content: center;
.linked-list__section:not(:first-child) & {
height: var(--item-height);
}
@apply text-2xs font-medium text-gray-700 dark:text-gray-200;
+ ul {
@apply rounded-t-none;
}
}

.linked-list__section-marker-label {
@apply max-w-[8rem] shrink-0 select-none pt-1.5 rounded-t-xl bg-gray-100 dark:bg-transparent;
max-width: unset;
width: 100%;
}

ul {
margin: 0;
list-style: none;
display: grid;
gap: 0.5rem;
@apply p-1.5 px-1.5 bg-gray-100 rounded-lg dark:bg-gray-900/60;

li {
/* Use a grid instead of flexbox so we can force things into column 1 and 2.
If we use flexbox then invisible items such as .linked-list__extra-leap-connector will take up an flex child space, changing the layout of items with a leap connector. */
display: grid;
grid-template-columns: 1rem 1fr;
> * {
grid-row: 1;
}
& > svg {
grid-column: 1;
}
.linked-list__field-name {
grid-column: 2;
}
gap: 0.5rem;
height: var(--item-height);
padding-block: var(--item-padding);
@apply px-3 border-1 border-gray-300 rounded-md bg-white shadow-ui-xs text-xs text-gray-850 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200 dark:shadow-none;
}

/* GROUP LOGIC TREE CONNECTORS / SOURCE
=================================================== */
.linked-list__connector {
anchor-name: --start-connection;
anchor-scope: --start-connection; /* one anchor per source row */

/* GROUP LOGIC TREE CONNECTORS / DIRECT LEAP
=================================================== */
/* ::before spans source right edge → gap midpoint.
::after spans gap midpoint → destination left edge. */
&::before,
&::after {
content: '';
position: absolute;
pointer-events: none;
border: var(--join-line);
right: calc(anchor(left var(--end-connection)) + var(--gap) / 2);
left: anchor(right --start-connection);
}

&::before {
top: anchor(bottom var(--end-connection));
bottom: anchor(center --start-connection);
border-left-color: transparent;
border-top-color: transparent;
border-radius: 0 0 var(--join-radius) 0;
}

&::after {
top: auto;
right: anchor(left var(--end-connection));
bottom: calc(anchor(bottom var(--end-connection)) - var(--join-stroke));
left: calc(anchor(left var(--end-connection)) - var(--gap) / 2 - var(--join-stroke));
height: var(--join-radius);
border-right-color: transparent;
border-bottom-color: transparent;
border-radius: var(--join-radius) 0 0 0;
}
}
/* GROUP LOGIC TREE CONNECTORS / PAGE LEAP (SOURCE → NON-ADJACENT PAGE)
=================================================== */
/* Overrides the direct-hop ::before to terminate at the buffer rail instead of the source centre, leaving room for the bridge element. */
.linked-list__page-leap {
&::before,
&::after {
border-style: dashed;
}
&::before {
bottom: calc(anchor(center --start-connection) + var(--buffer-top-pull));
left: calc(anchor(right --start-connection) + var(--gap));
}
}
}
}

/* GROUP LOGIC TREE CONNECTORS / VIEW MODES
=================================================== */
.linked-list__field-name {
@apply line-clamp-1 select-none;
}

.linked-list--expanded {
/* Bit of a magic number here to pull the line into the gap between the items */
--buffer-top-pull: calc(1.45rem + var(--item-padding));
--item-height: 4.5rem;
.linked-list__field-name {
@apply line-clamp-2;
}
}

/* GROUP LOGIC TREE CONNECTORS / FIELDSETS
=================================================== */
.linked-list__fieldset-field {
@apply border-dashed border-gray-400/75! dark:border-gray-600! opacity-75;
}

/* GROUP LOGIC TREE CONNECTORS / LEAP BRIDGE (FIRST GAP + CORNER)
=================================================== */
.linked-list__extra-leap-connector {
&::before {
top: calc(anchor(top --start-connection) + var(--buffer-top-pull) / 2 - var(--join-stroke) * 4);
bottom: anchor(center --start-connection);
left: anchor(right --start-connection);
width: calc(var(--gap) - var(--buffer-left-pull));
border-block-end: var(--join-line);
border-inline-end: var(--join-line);
border-bottom-right-radius: var(--join-radius);
}

&::after {
/* display: none; */
top: calc(anchor(center --start-connection) - var(--buffer-top-pull) - var(--join-stroke));
bottom: calc(anchor(center --start-connection) + var(--buffer-top-pull) / 2 + var(--join-stroke));
width: calc(var(--buffer-left-pull) + var(--join-stroke) * 2);
left: calc(anchor(right --start-connection) + var(--gap) - var(--buffer-left-pull) - var(--join-stroke));
border-inline-start: var(--join-line);
border-block-start: var(--join-line);
border-top-left-radius: var(--join-radius);
}

&::before,
&::after {
border-style: dashed;
content: '';
position: absolute;
pointer-events: none;
}
}
}
36 changes: 32 additions & 4 deletions resources/css/core/utilities.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@
display: none;
}

@utility max-h-screen-px {
max-height: calc(100vh - 1px) !important;
}

.z-max {
z-index: var(--z-index-max);
}
Expand Down Expand Up @@ -76,6 +72,38 @@
}
}

/*
Break out of .content-card horizontal padding and [data-max-width-wrapper] max-w-page
so content can span the card while staying aligned with padded siblings on scroll.

HTML Example:
<div class="content-card">
(direct descendants only)
<div class="st-full-bleed-content">
<div>…wide / scrollable content…</div>
</div>
</div>
*/
@utility st-full-bleed-content {
@apply -mx-2 sm:-mx-6 md:-mx-12 min-w-0;

& > * {
@apply px-2 sm:px-6 md:px-12 min-w-0 pb-6;
}
}
.content-card:has(.st-full-bleed-content) {
min-width: 0;
overflow-x: clip;
padding-bottom: 0;
}
[data-max-width-wrapper]:has(.st-full-bleed-content) {
max-width: none !important;
}

@utility max-h-screen-px {
max-height: calc(100vh - 1px) !important;
}

/* UTILITIES / TEXT
=================================================== */
/* Prefix things with `st-text-` (st for Statamic) because `text-` is a Tailwind prefix */
Expand Down
1 change: 1 addition & 0 deletions resources/css/cp.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
@import './components/notifications.css';
@import './components/page-tree.css';
@import './components/forms.css';
@import './components/forms/logic-tree.css';
@import './components/pagination.css';
@import './components/popover.css';
@import './components/preview.css';
Expand Down
13 changes: 13 additions & 0 deletions resources/js/components/forms/FieldNumber.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script setup>
defineProps({
number: { type: Number, default: null },
});
</script>

<template>
<span
v-if="number"
data-field-number
class="font-mono text-2xs text-gray-500 tabular-nums dark:text-gray-400"
>{{ number }}.</span>
</template>
22 changes: 22 additions & 0 deletions resources/js/components/forms/FieldNumberingToggle.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script setup>
import { ToggleGroup, ToggleItem } from '@ui';
import { useFieldNumberingPreference } from '@/composables/forms/field-numbering';

const { showFieldNumbers } = useFieldNumberingPreference();

const toggleFieldNumbers = () => {
showFieldNumbers.value = ! showFieldNumbers.value;
};
</script>

<template>
<ToggleGroup :model-value="showFieldNumbers ? 'on' : null" size="xs">
<ToggleItem
value="on"
icon="mail-sign-hashtag"
:aria-label="__('Show field numbers')"
v-tooltip="__('Show field numbers')"
@click.prevent="toggleFieldNumbers"
/>
</ToggleGroup>
</template>
Loading
Loading