diff --git a/config/assets.php b/config/assets.php index b91ecb9104e..91d157389d4 100644 --- a/config/assets.php +++ b/config/assets.php @@ -296,4 +296,19 @@ 'folder' => 'set-previews', ], + /* + *-------------------------------------------------------------------------- + * Image Choice Images + *-------------------------------------------------------------------------- + * + * The Image Choice fieldtype on forms allows you to present images for a + * visitor to choose from. Here you may specify the asset container and + * folder where these images are stored. + */ + + 'image_choice_images' => [ + 'container' => 'assets', + 'folder' => null, + ], + ]; diff --git a/config/forms.php b/config/forms.php index c2d434f0289..37f94e2d256 100644 --- a/config/forms.php +++ b/config/forms.php @@ -13,6 +13,18 @@ 'forms' => resource_path('forms'), + /* + |-------------------------------------------------------------------------- + | File Uploads Path + |-------------------------------------------------------------------------- + | + | The path (on the file uploads disk) where file uploads are stored + | before they're converted to an asset or deleted. + | + */ + + 'file_uploads_path' => env('STATAMIC_FORM_UPLOADS_PATH', 'statamic/form-uploads'), + /* |-------------------------------------------------------------------------- | Email View Folder @@ -35,6 +47,18 @@ 'send_email_job' => \Statamic\Forms\SendEmail::class, + /* + |-------------------------------------------------------------------------- + | Partial Submissions + |-------------------------------------------------------------------------- + | + | Partial submissions are automatically deleted after a set number of days. + | Set this to null to prevent their automatic deletion. + | + */ + + 'delete_partial_submissions_after' => 7, + /* |-------------------------------------------------------------------------- | Exporters diff --git a/lang/en/form-fieldtypes.php b/lang/en/form-fieldtypes.php new file mode 100644 index 00000000000..7eb6cb68e4f --- /dev/null +++ b/lang/en/form-fieldtypes.php @@ -0,0 +1,5 @@ + "This won't be displayed to visitors. It's just for internal reference.", +]; diff --git a/lang/en/messages.php b/lang/en/messages.php index f6339ba62df..aefafc6ced2 100644 --- a/lang/en/messages.php +++ b/lang/en/messages.php @@ -87,7 +87,7 @@ 'entry_count' => ':count entry|:count entries', 'entry_origin_instructions' => 'The new localization will inherit values from the entry in the selected site.', 'expect_root_instructions' => 'Consider the first page in the tree a "root" or "home" page.', - 'field_conditions_always_save_instructions' => 'Always save field value, even if the field is hidden.', + 'field_conditions_always_save_instructions' => 'Always save the field value, even if the field is hidden.', 'field_conditions_field_instructions' => 'You may enter any field handle. You are not limited to the options in the dropdown.', 'field_conditions_instructions' => 'When to show or hide this field.', 'field_desynced_from_origin' => 'Desynced from origin. Click to sync and revert to the origin\'s value.', @@ -136,10 +136,31 @@ 'form_configure_honeypot_instructions' => 'Field name to use as a honeypot. Honeypots are special fields used to reduce bot spam.', 'form_configure_intro' => 'Forms collect information from visitors and can trigger events and send notifications when submissions are received.', 'form_configure_mailer_instructions' => 'Choose the mailer for sending this email. Leave blank to fall back to the default mailer.', + 'form_configure_generate_fake_submissions_instructions' => 'Allow generating fake submissions and workflow testing from the submissions screen.', 'form_configure_store_instructions' => 'Disable to stop storing submissions. Events and email notifications will still be sent.', 'form_configure_title_instructions' => 'Use a call to action, such as \'Contact Us\'.', + 'form_configure_close_date_instructions' => 'The form will stop accepting submissions after this date. Leave blank to never close.', + 'form_configure_submission_limit_instructions' => 'The maximum number of submissions to accept. Leave blank for no limit.', + 'form_configure_submission_limit_period_instructions' => 'How often the submission limit resets.', + 'form_configure_closed_message_instructions' => 'Shown when the form is closed or the submission limit has been reached. Leave blank to use the default message.', + 'form_configure_require_login_instructions' => 'Only allow logged in users to submit this form.', + 'form_configure_require_login_message_instructions' => 'Shown when a logged out visitor tries to submit the form. Leave blank to use the default message.', + 'form_closed_message' => 'This form is no longer accepting submissions.', + 'form_require_login_message' => 'You must be logged in to submit this form.', 'form_create_description' => 'Get started by creating your first form.', + 'form_builder' => 'Form Builder', + 'form_builder_empty_inspector' => 'Drag fields onto the form to add them. Click a field, section, or page to edit its settings here.', 'form_export_filtered_description' => 'Exports submissions with current filters and visible columns.', + 'form_fake_submissions_deleted' => 'Deleted :count fake submission.|Deleted :count fake submissions.', + 'form_fake_submissions_delete_failed' => 'Fake submissions could not be deleted.', + 'form_fake_submissions_form_not_found' => 'Form could not be found for deleting fake submissions.', + 'form_fake_submissions_none_to_delete' => 'No fake submissions to delete.', + 'form_fake_submissions_partial_delete' => ':success/:total fake submissions were deleted.', + 'form_fake_submission_generation_disabled' => 'Generating fake submissions is disabled for this form.', + 'form_fake_submission_cancelled' => 'Fake submission was cancelled by a form event listener.', + 'form_fake_submission_generated' => 'Fake submission generated.', + 'form_fields_instructions_instructions' => 'Additional field instructions like this.', + 'form_navigation' => 'Form navigation', 'getting_started_widget_collections' => 'Collections hold the different content types that make up your site, helping you stay organized.', 'getting_started_widget_docs' => 'Discover everything Statamic can do, and learn how to use its powerful features the right way.', 'getting_started_widget_header' => 'Getting Started with Statamic', diff --git a/lang/en/permissions.php b/lang/en/permissions.php index dae704fcd3e..054778691b3 100644 --- a/lang/en/permissions.php +++ b/lang/en/permissions.php @@ -72,8 +72,14 @@ 'group_forms' => 'Forms', 'configure_forms' => 'Configure forms', 'configure_forms_desc' => 'Grants access to all form related permissions', - 'configure_form_fields' => 'Configure Form Fields', - 'configure_form_fields_desc' => 'Ability to edit form blueprints, fieldsets, and their fields.', + 'edit_forms' => 'Edit forms', + 'edit_forms_desc' => 'Grants access to all forms, except their submissions', + 'create_forms' => 'Create forms', + 'delete_forms' => 'Delete forms', + 'view_form_submissions' => 'View submissions', + 'view_form_submissions_desc' => 'Grants access to submissions across all forms', + 'delete_form_submissions' => 'Delete submissions', + 'edit_{form}_form' => 'Edit :form form', 'view_{form}_form_submissions' => 'View :form submissions', 'delete_{form}_form_submissions' => 'Delete :form submissions', diff --git a/resources/css/components/fieldtypes/assets.css b/resources/css/components/fieldtypes/assets.css index f07290edd8f..7fee0ddf4d6 100644 --- a/resources/css/components/fieldtypes/assets.css +++ b/resources/css/components/fieldtypes/assets.css @@ -30,6 +30,21 @@ } } +/* Single asset picker (e.g. Image Choice option image) + ========================================================================== */ + +/* Remove the double border effect */ +.single_asset-fieldtype .asset-tile { + outline: none; + img { + @apply rounded-[calc(var(--radius-sm)-1px)]!; + } + :has(> &) { + padding: 0; + padding-block-end: 3px; + } +} + /* In a Bard fieldtype ========================================================================== */ diff --git a/resources/css/components/fieldtypes/star-rating.css b/resources/css/components/fieldtypes/star-rating.css new file mode 100644 index 00000000000..a63b92450db --- /dev/null +++ b/resources/css/components/fieldtypes/star-rating.css @@ -0,0 +1,100 @@ +/* CSS-only star rating via range input — https://css-tricks.com/a-css-only-star-rating-component-and-more-part-1/ */ + +.star-rating-input { + --s: 1.5rem; + --star-rating-gap: 0.4rem; + --star-rating-max: 5; + --star-rating-step: 1; + --_cell: calc(var(--s) + var(--star-rating-gap)); + --_pad: calc(var(--star-rating-step) * var(--_cell) / 2); + /* Thumb fill boundary: full steps use half a cell; half steps use half a star width. */ + --_fill-offset: calc(var(--_cell) / 2); + --_fill: var(--color-primary, goldenrod); + --_empty: var(--color-gray-600, #52525b); + /* viewBox width = 14 + 14 * (--star-rating-gap / --s) = 17.7333; path from resources/svg/icons/star.svg */ + --_star-stroke-mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2017.7333%2014%22%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%220.75%22%20stroke-linejoin%3D%22round%22%20d%3D%22M9.26344%204.34844C8.94942%203.02262%208.45287%201.8971%207.7007%200.865168%207.34583%200.378308%206.65711%200.378311%206.302%200.864976%205.54897%201.89696%205.0506%203.02254%204.73655%204.34844c-1.27708-0.0475-2.4531%200.10502-3.61903%200.49806-0.567297%200.19124-0.797993%200.88418-0.460427%201.39727C1.35657%207.30695%202.22018%208.14266%203.33681%208.8471c-0.43299%201.2339-0.63943%202.4396-0.65185%203.7051-0.00626%200.6378%200.56948%201.0938%201.15274%200.9045%201.16232-0.3774%202.18286-0.9657%203.16091-1.8293%200.97815%200.8637%202.00043%201.452%203.16369%201.8294%200.5833%200.1892%201.159-0.2668%201.1527-0.9046-0.0124-1.2655-0.2188-2.4712-0.6518-3.7051%201.1166-0.70444%201.9802-1.54015%202.6797-2.60333%200.3376-0.51309%200.1069-1.20603-0.4604-1.39727-1.1659-0.39304-2.342-0.54556-3.61906-0.49806Z%22%2F%3E%3C%2Fsvg%3E"); + --_star-fill-mask: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2017.7333%2014%22%3E%3Cpath%20fill%3D%22%23000%22%20d%3D%22M9.26344%204.34844C8.94942%203.02262%208.45287%201.8971%207.7007%200.865168%207.34583%200.378308%206.65711%200.378311%206.302%200.864976%205.54897%201.89696%205.0506%203.02254%204.73655%204.34844c-1.27708-0.0475-2.4531%200.10502-3.61903%200.49806-0.567297%200.19124-0.797993%200.88418-0.460427%201.39727C1.35657%207.30695%202.22018%208.14266%203.33681%208.8471c-0.43299%201.2339-0.63943%202.4396-0.65185%203.7051-0.00626%200.6378%200.56948%201.0938%201.15274%200.9045%201.16232-0.3774%202.18286-0.9657%203.16091-1.8293%200.97815%200.8637%202.00043%201.452%203.16369%201.8294%200.5833%200.1892%201.159-0.2668%201.1527-0.9046-0.0124-1.2655-0.2188-2.4712-0.6518-3.7051%201.1166-0.70444%201.9802-1.54015%202.6797-2.60333%200.3376-0.51309%200.1069-1.20603-0.4604-1.39727-1.1659-0.39304-2.342-0.54556-3.61906-0.49806Z%22%2F%3E%3C%2Fsvg%3E"); + + position: relative; + display: block; + width: calc(var(--_cell) * var(--star-rating-max)); + max-width: 100%; + height: var(--s); + /* Symmetric padding aligns thumb centres with star slots (CSS-Tricks). */ + padding-inline: var(--_pad); + box-sizing: border-box; + appearance: none; + cursor: pointer; + background: transparent; + border: none; + mask-image: var(--_star-fill-mask); + mask-size: var(--_cell) var(--s); + mask-repeat: repeat; + + &[style*='--star-rating-step: 0.5'] { + --_fill-offset: calc((var(--s) - var(--star-rating-gap)) / 4); + } + + &::before { + content: ''; + position: absolute; + inset: 0; + background-color: var(--_empty); + pointer-events: none; + mask-image: var(--_star-stroke-mask); + mask-size: var(--_cell) var(--s); + mask-repeat: repeat; + } + + :where(.dark) & { + --_empty: var(--color-gray-500); + } + + &:disabled { + cursor: not-allowed; + opacity: 0.6; + } + + &::-webkit-slider-thumb { + appearance: none; + -webkit-appearance: none; + width: 1px; + height: 0; + background: transparent; + border: none; + border-image: conic-gradient(at calc(50% + var(--_fill-offset)), transparent 50%, var(--_fill) 0) fill 0 // var(--s) 500px; + } + + &::-moz-range-thumb { + appearance: none; + width: 1px; + height: 0; + background: transparent; + border: none; + border-image: conic-gradient(at calc(50% + var(--_fill-offset)), transparent 50%, var(--_fill) 0) fill 0 // var(--s) 500px; + } + + &.star-rating-input--unrated { + &::-webkit-slider-thumb { + border-image: conic-gradient(transparent 0) fill 0 // var(--s) 500px; + } + + &::-moz-range-thumb { + border-image: conic-gradient(transparent 0) fill 0 // var(--s) 500px; + } + } + + &::-webkit-slider-runnable-track { + appearance: none; + -webkit-appearance: none; + height: var(--s); + background: transparent; + } + + &::-moz-range-track { + appearance: none; + height: var(--s); + background: transparent; + border: none; + } +} diff --git a/resources/css/components/fieldtypes/table.css b/resources/css/components/fieldtypes/table.css index e0d90804cdc..a45df03336b 100644 --- a/resources/css/components/fieldtypes/table.css +++ b/resources/css/components/fieldtypes/table.css @@ -3,7 +3,7 @@ Table Fieldtype ========================================================================== */ .table-contained { - @apply relative mb-4 w-full rounded-lg border shadow-ui-sm outline-hidden dark:border-gray-700 with-contrast:border-gray-500 text-start; + @apply relative mb-4 w-full rounded-lg bg-content-bg border shadow-ui-sm outline-hidden dark:border-gray-700 with-contrast:border-gray-500 text-start; border-collapse: separate; border-spacing: 0; table-layout: fixed; @@ -107,6 +107,10 @@ Table Fieldtype @apply border-e-0; } + &:has(+ .visibility-controls) { + @apply border-e-0; + } + &.table-drag-handle { @apply w-3 p-2 bg-white dark:bg-gray-850; &:hover { @@ -122,9 +126,13 @@ Table Fieldtype @apply border-none bg-white dark:bg-gray-900 text-sm shadow-none; } - .row-controls { + .row-controls, + .visibility-controls { @apply w-8 text-center ps-0; vertical-align: middle; + } + + .row-controls { &:focus-within { @apply bg-gray-100 dark:bg-gray-700; button { diff --git a/resources/css/components/forms.css b/resources/css/components/forms.css new file mode 100644 index 00000000000..8bc2e4d7153 --- /dev/null +++ b/resources/css/components/forms.css @@ -0,0 +1,415 @@ +[data-ui-input-group] { + /* Lock in logic icons */ + position: relative; +} + +/* GROUP MAIN COLUMN / FIELD BACKGROUND DECORATION +=================================================== */ +[data-editing-field] { + position: relative; + scroll-margin-top: 7rem; + > * { + /* Pull everything inside above */ + position: relative; + } + /* Field Editing */ + &:not(button) { + &::before { + content: ''; + position: absolute; + /* Bit of optical alignment here */ + inset: -0.5rem calc(-1rem - 2px) -0.75rem; + border: 1px dashed var(--color-blue-400); + border-radius: 10px; + @apply bg-blue-50; + } + &:has([data-form-banner]) { + &::before { + /* Exception for the banner since it's more uniform */ + inset-block-end: -0.5rem; + } + &::before, + [data-editing-field-actions] { + background: transparent; + } + } + &[data-first-row]::before { + inset-block-start: -1.25rem; + } + &[data-last-row]::before { + inset-block-end: -1.25rem; + } + :where(.dark) &::before { + @apply bg-blue-950 border-blue-500/75; + } + } + /* Pull the field actions up a bit for the first row */ + &[data-first-row] { + [data-editing-field-actions] { + top: -0.85rem; + } + } + /* Editing a button like "Next page" and "Submit" */ + :has(> button&) { + position: relative; + &::before { + content: ''; + position: absolute; + inset: -1rem calc(-1rem - 2px) -1.25rem; + border: 1px dashed var(--color-blue-400); + border-radius: 10px; + @apply bg-blue-50; + } + :where(.dark) &::before { + @apply bg-blue-950 border-blue-500/75; + } + } +} + + +/* GROUP MAIN COLUMN / FIELDSETS +=================================================== */ +[data-fieldset-group] { + anchor-scope: --fieldset-start, --fieldset-end; +} + +[data-fieldset-label] { + @apply bg-content-bg text-indigo-800 dark:text-indigo-400/85; + position: absolute; + display: inline-flex; + top: calc(anchor(--fieldset-start top) + 2rem); + left: calc(anchor(--fieldset-start left) - 2.5rem); + width: 0.75rem; + svg { + rotate: 90deg; + } +} + +#fieldset-start { + anchor-name: --fieldset-start; + + /* The connector */ + &::before { + @apply border border-dashed border-indigo-500; + content: ''; + position: absolute; + width: 3rem; + top: calc(anchor(--fieldset-start top) + 0.5rem); + left: calc(anchor(--fieldset-start left) - 2rem); + bottom: anchor(--fieldset-end bottom); + border-radius: 10px; + clip-path: polygon(0 0, 30% 0, 30% 100%, 0 100%); + } +} + +:where(.dark) #fieldset-start::before { + @apply border-indigo-400/50; +} + +#fieldset-end { + anchor-name: --fieldset-end; +} + +/* Hide anchors and absolutely positioned logic icons unless the panel is open. When the panel collapses, elements like anchors are slow to hide because of the way they're rendered. */ +[data-panel-collapsed="true"] { + [data-fieldset-label], + [data-logic-attached], + #fieldset-start::before { + opacity: 0; + transition: opacity 0.05s ease-in; + } +} + + +/* GROUP MAIN COLUMN / COLLAPSIBLE FIELDS +=================================================== */ +/* Field Icons (hide by default) */ +[data-collapsed-field-icon] { + display: none; +} + +/* GROUP MAIN COLUMN / COLLAPSIBLE FIELDS / COLLAPSED STATE +=================================================== */ +[data-fields-collapsed="true"] { + /* Hide the inputs */ + [data-ui-input-group] { + [data-ui-description], + > :not(:first-child, :has([data-logic-attached])) { + display: none; + } + + * { + margin-bottom: 0; + } + } + + [data-editing-item] { + /* Tweak the actions position since the surrounding space is different */ + [data-editing-field-actions] { + top: 50%; + transform: translateY(calc(-50% - 0.5px)); + @apply pe-3; + } + + &[data-first-row]::before, + &[data-last-row]::before, + &::before { + inset: calc(0% - 1px); + } + } + + /* Field Icons */ + [data-collapsed-field-icon] { + display: inline-flex; + } + + /* Compress spacing for fieldsets when the fields are collapsed */ + [data-fieldset-group] { + @apply space-y-5; + } + + /* Wrapper/white background */ + [data-section-drop-zone]:has(&) { + background: unset; + box-shadow: unset; + padding: 0; + } + + [data-field-item] { + @apply px-3 sm:px-5 py-2.75 bg-white dark:bg-gray-850 rounded-xl ring ring-gray-200 dark:ring-gray-700/80 shadow-ui-md; + } + + ~ [data-pagination] { + @apply mt-4; + /* Editing a button like "Next page" and "Submit" */ + :has(> button)::before { + inset: calc(0% - 1px); + } + } +} + + +/* GROUP SIDEBAR / EDITING ITEM DIRECTION +=================================================== */ +/* No decoration of the editing item itself, just a hook to handle the editing item direction indicators. */ +#main-content { + timeline-scope: --editing-field-scroll; +} +[data-editing-item] { + view-timeline-name: --editing-field-scroll; +} + +/* Page 1 of 2, etc. */ +[data-form-page-label] { + scroll-margin-top: 7rem; +} + +[data-field-direction-down], +[data-field-direction-up] { + @apply p-1 size-4.75! ms-1 text-blue-500 dark:text-blue-400 bg-blue-50 dark:bg-blue-950 border-1 border-dotted border-blue-400 dark:border-blue-700 rounded-full; +} + +@keyframes field-direction-fade-in { + 50% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +[data-field-direction-up] { + opacity: 0; + animation: field-direction-fade-in linear both; + animation-timeline: --editing-field-scroll; + animation-range: exit 0%, exit 100%; +} + +@keyframes field-direction-fade-out { + to { + opacity: 0; + } +} + +[data-field-direction-down] { + animation: field-direction-fade-out linear both; + animation-timeline: --editing-field-scroll; + animation-range: entry 50%, entry 100%; +} + + +/* GROUP SIDEBAR / LOGIC TEXT / VERTICAL / LAYOUT +=================================================== */ +.logic-text { + --inner-indent: 1.75rem; + :where(.dark) & { + --logic-line: var(--color-gray-600); + } + + /* Outer structure */ + > ol { + /* Optically align */ + margin-inline-start: -0.1rem; + /* Inner structure */ + ol { + display: grid; + grid-template-columns: 100%; + @apply gap-2 mb-2.5; + &:not(.indented-condition *) { + margin-inline-start: var(--inner-indent); + } + li { + /* Prevent full-width comboboxes */ + > .w-full { + display: inline-flex; + width: unset; + min-width: unset; + max-width: 100%; + } + } + } + } + /* Make the input field automatically adjust to the content size */ + [data-ui-input] input { + field-sizing: content; + } +} + +.logic-text-badge { + @apply + py-1.5 px-3 + bg-content-bg + text-gray-800 dark:text-gray-300 + rounded-full + text-sm + shadow-ui-sm + border-1 + border-gray-300 dark:border-gray-600 + ; + display: inline-block; + user-select: none; + text-align: center; +} + +.logic-text__pill { + display: flex; + max-width: fit-content; + align-items: center; + @apply + gap-1 + rounded-full + bg-content-bg + px-3 py-1 + shadow-ui-sm + text-gray-900 dark:text-gray-100 + border-1 + border-gray-300 + dark:border-1 dark:border-gray-600 + ; +} + +.logic-text__pill-icon { + display: flex; + flex-shrink: 0; + align-items: center; + justify-content: center; +} + +.logic-text__pill-text { + @apply + text-sm + text-gray-900 dark:text-gray-100 + ; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + user-select: none; +} + +/* GROUP SIDEBAR / LOGIC TEXT / VERTICAL / CONNECTORS +=================================================== */ +.logic-text { + --logic-line: 1px dashed var(--color-gray-400); + --outer-indent-connector: calc(1rem - 2px); + @apply mb-0; + /* Inner structure */ + ol ol { + position: relative; + &::before { + content: ''; + position: absolute; + z-index: var(--z-index-below); + inset-inline-start: 1.15rem; + border-inline-start: var(--logic-line); + height: 100%; + } + } + /* Exit "If" */ + > ol > li { + &:not(:last-child) { + + li { + position: relative; + &::before { + content: ''; + position: absolute; + z-index: var(--z-index-below); + top: -1.5rem; + left: calc(0% + var(--inner-indent) / 2); + width: calc(var(--spacing) * 4); + height: 3.5rem; + border: var(--logic-line); + border-inline-end: 0; + border-block-end: 0; + border-start-start-radius: 100vw; + } + } + } + } +} +.logic-text__condition { + position: relative; + /* e.g. not a "to" badge inside a calculation condition */ + &:not(ol ol *) { + @apply mb-2.5; + &::after { + content: ''; + position: absolute; + left: var(--outer-indent-connector); + border: var(--logic-line); + border-inline-end: 0; + border-block-start: 0; + border-end-start-radius: 100vw; + top: 100%; + width: calc(var(--spacing) * 4); + /* Don't overlap/clash with subsequent connectors */ + height: 85%; + } + } +} + +/* GROUP SIDEBAR / LOGIC TEXT / VERTICAL / HORIZONTAL ORIENTATION +=================================================== */ +@container (width >= 500px) { + .logic-text { + /* Outer structure */ + > ol { + > li { + &:not(:last-child) { + li:last-child { + /* Pull logic connectors back to the left */ + position: unset; + } + } + } + /* Inner structure */ + ol { + display: flex; + flex-wrap: wrap; + &::before { + /* Hide connectors for now-horizontal logic */ + content: unset; + } + } + } + } +} diff --git a/resources/css/components/forms/logic-tree.css b/resources/css/components/forms/logic-tree.css new file mode 100644 index 00000000000..06b1879f378 --- /dev/null +++ b/resources/css/components/forms/logic-tree.css @@ -0,0 +1,396 @@ +/* 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 +