diff --git a/src/css/common/_badges.scss b/src/css/common/_badges.scss index a2ad6bd90..0ccdbf5e2 100644 --- a/src/css/common/_badges.scss +++ b/src/css/common/_badges.scss @@ -125,3 +125,31 @@ } } } + +// Override WordPress' .wp-core-ui .button .dashicons { line-height: 1.9 } — +// inside a button the inflated line-box would shift the glyph below the +// badge's bottom border. Placed at end-of-file so it follows all other +// .dashicons rules in source order (no-descending-specificity). +.wp-core-ui .button .badge .dashicons { + line-height: 1; +} + + +// Pro badge consolidated to the navigation pro-chip style: a light accent pill +// in every context, overriding the badge colour map and the inverted/nav-tab +// variants (whose white !important border must be beaten here). This is a +// deliberate final override, so it intentionally follows higher-specificity +// context rules above. +/* stylelint-disable no-descending-specificity */ +.badge.pro-badge, +.inverted-badges .badge.pro-badge, +.nav-tab-inactive .badge.pro-badge { + color: #2271b1; + background-color: #eff5f9; + border: 1px solid rgb(34 113 177 / 10%) !important; + border-radius: 999px; + padding-block: 3px; + padding-inline: 10px; + line-height: normal; +} +/* stylelint-enable no-descending-specificity */ diff --git a/src/css/common/_cards.scss b/src/css/common/_cards.scss index f0b53c2c6..bb2d08469 100644 --- a/src/css/common/_cards.scss +++ b/src/css/common/_cards.scss @@ -8,7 +8,7 @@ .code-snippets-card { background: #fff; border: 1px solid #c3c4c7; - border-radius: 6px; + border-radius: 5px; margin: 0; display: flex; flex-flow: column; @@ -25,19 +25,46 @@ footer { display: flex; + align-items: center; + justify-content: space-between; gap: 8px; - background: #f6f7f7; + background: #f7f7f8; margin-block-start: auto; border-block-start: 1px solid #c3c4c7; padding-inline: 24px; padding-block: 12px; + border-end-start-radius: 5px; + border-end-end-radius: 5px; + } + + // The status label shrinks and clips away under pressure rather than + // pushing the action buttons onto a second line. + .snippet-card-footer-status { + display: flex; align-items: center; - border-end-start-radius: 6px; - border-end-end-radius: 6px; + gap: 8px; + flex: 0 1 auto; + min-inline-size: 0; + overflow: hidden; + white-space: nowrap; } - &.is-selectable, - &.has-corner-controls { + .snippet-card-footer-actions { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: nowrap; + gap: 8px; + flex: 0 0 auto; + + .button:not(.kebab-menu-trigger) { + min-inline-size: 103px; + text-align: center; + justify-content: center; + } + } + + &.is-selectable { position: relative; } @@ -54,12 +81,39 @@ } } - &.is-selectable .card-inner h3 { - margin-inline-end: 48px; + input[type='checkbox'].snippet-card-select { + appearance: none; + display: grid; + place-content: center; + inline-size: 20px; + block-size: 20px; + padding: 0; + box-sizing: border-box; + background: #fff; + border: 1.5px solid #2271b1; + border-radius: 5px; + box-shadow: 0 2px 2px rgb(0 0 0 / 5%); + cursor: pointer; + + &::before { + content: none; + } + + &:checked { + background: #2271b1; + + &::before { + content: ''; + inline-size: 14px; + block-size: 14px; + margin: 0; + background: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M14.83 4.89l1.34.94-5.81 8.38H9.02L5.78 9.67l1.34-1.25 2.57 2.4z' fill='%23fff'/%3E%3C/svg%3E") center/14px no-repeat; + } + } } - &.has-corner-controls .card-inner h3 { - margin-inline-end: 96px; + &.is-selectable .card-inner > h3 { + margin-inline-end: 48px; } &.is-selected { diff --git a/src/css/common/_kebab-menu.scss b/src/css/common/_kebab-menu.scss new file mode 100644 index 000000000..061bc0eb7 --- /dev/null +++ b/src/css/common/_kebab-menu.scss @@ -0,0 +1,104 @@ +// Generic kebab ("more actions") menu: a square accent-outlined trigger +// button that opens a small popover of actions anchored to its +// bottom-end corner, flipping above the trigger near the viewport edge. +.kebab-menu { + position: relative; + display: inline-flex; +} + +.kebab-menu-trigger { + display: flex; + align-items: center; + justify-content: center; + inline-size: 38px; + block-size: 38px; + box-sizing: border-box; + padding: 0; + background: #fff; + color: #2271b1; + border: 1px solid #2271b1; + border-radius: 5px; + cursor: pointer; + + svg { + display: block; + } + + &:hover, + &:focus { + background: #f0f6fc; + } +} + +.kebab-menu-popover { + position: absolute; + inset-block-start: calc(100% + 4px); + inset-inline-end: 0; + z-index: 1000; + inline-size: 224px; + box-sizing: border-box; + margin: 0; + padding-block: 6px; + padding-inline: 0; + list-style: none; + background: #fff; + border: 1px solid #e2e2e4; + border-radius: 5px; + box-shadow: 0 3px 16px rgb(0 0 0 / 15%); + + &.kebab-menu-popover-top { + inset-block: auto calc(100% + 4px); + } + + li { + margin: 0; + } +} + +.kebab-menu-item { + display: block; + inline-size: 100%; + box-sizing: border-box; + padding-block: 10px; + padding-inline: 14px; + background: none; + border: none; + font-family: inherit; + font-size: 14px; + line-height: 1.4; + color: #2c3337; + text-align: start; + cursor: pointer; + + &:hover, + &:focus { + background: #f0f0f1; + } + + &:disabled { + color: #a7aaad; + cursor: default; + background: none; + } + + &.kebab-menu-item-destructive { + color: #d63638; + } +} + +.kebab-menu-divider { + margin-block: 6px; + border-block-start: 1px solid #e2e2e4; +} + +.kebab-menu-row { + display: flex; + align-items: center; + gap: 8px; + box-sizing: border-box; + padding-block: 10px; + padding-inline: 14px; + font-size: 14px; + line-height: 1.4; + color: #2c3337; +} diff --git a/src/css/common/_list-table.scss b/src/css/common/_list-table.scss index 341cc2e1c..89c5eb866 100644 --- a/src/css/common/_list-table.scss +++ b/src/css/common/_list-table.scss @@ -1,313 +1,4 @@ -@use 'theme'; - -.column-name { - .extra-icons { - float: inline-end; - display: flex; - flex-wrap: wrap; - gap: 5px; - } - - .dashicons-lock { - color: #646970; - opacity: 0.7; - - &:hover { - opacity: 1; - } - } -} - -.active-snippet { - td, th { - background-color: rgba(#78c8e6, 0.06); - } - - th.check-column { - border-inline-start: 2px solid #2ea2cc; - } - - .column-name > .snippet-name { - font-weight: 600; - } -} - -.inactive-snippet { - @include theme.link-colors(#579); -} - -.paging-input { - display: inline-flex; - align-items: center; - gap: 2px; -} - -.wp-list-table { - td.column-id { - text-align: center; - } - - tr { - background: #fff; - } - - ol, ul { - margin-block: 0 1.5em; - margin-inline: 1.5em 0; - } - - ul { - list-style: disc; - } - - .sortable-column-title { - display: flex; - } - - th.sortable .list-table-sort-button, - th.sorted .list-table-sort-button { - display: flex; - flex-direction: row; - align-items: center; - inline-size: 100%; - margin: 0; - border: none; - background: none; - font: inherit; - color: #2271b1; - cursor: pointer; - text-align: start; - overflow: hidden; - padding: 8px; - } - - th.sortable .list-table-sort-button:focus-visible, - th.sorted .list-table-sort-button:focus-visible { - outline: 2px solid #2271b1; - outline-offset: 2px; - border-radius: 2px; - } - - .row-actions { - color: #646970; - position: relative; - inset-inline-start: 0; - - // Row-action buttons render as plain inline links. On WP 7.0 the generic `.button` - // compatibility styling would otherwise give them a fill, border, radius and fixed - // height, so fully neutralise it here (this rule is emitted after the wp-admin layer - // at equal specificity, so it wins). Non-delete links inherit the accent text colour. - .button-link { - block-size: auto; - min-block-size: 0; - padding: 0; - border: 0; - border-radius: 0; - background: none; - box-shadow: none; - line-height: inherit; - font-weight: 400; - - &:hover:not(:disabled), - &:focus:not(:disabled) { - background: none; - border: 0; - box-shadow: none; - } - - &.delete { - color: #b32d2e; - } - - &.snippet-cloud-update { - color: #ff851b; - } - } - - .snippet-row-action-feedback, - .snippet-row-action-error { - display: inline-flex; - align-items: center; - gap: .5em; - - .components-spinner { - margin: 0; - inline-size: 1em; - block-size: 1em; - } - - } - - .snippet-row-action-error { - color: #b32d2e; - - } - - .delete.disabled { - color: #a7aaad; - cursor: not-allowed; - pointer-events: none; - } - } - - .column-activate { - padding-inline-end: 0 !important; - } - - .clear-filters { - vertical-align: middle; - } - - tfoot th.check-column { - padding: 13px 0 0 3px; - } - - thead th.check-column, - tfoot th.check-column, - .inactive-snippet th.check-column { - padding-inline-start: 5px; - } - - .active-snippet, .inactive-snippet { - td, th { - padding: 10px 9px; - box-shadow: inset 0 -1px 0 rgb(0 0 0 / 10%); - } - } - - tr.active-snippet + tr.inactive-snippet th, - tr.active-snippet + tr.inactive-snippet td { - border-block-start: 1px solid rgb(0 0 0 / 3%); - box-shadow: inset 0 1px 0 rgb(0 0 0 / 2%), inset 0 -1px 0 #e1e1e1; - } - - .delete { - color: #b32d2e; - } - - a.delete:not(.disabled) { - &:hover, &:focus, &:active { - border-block-end: 1px solid #f00; - color: #f00; - } - } - - td.column-date, th.column-date { - white-space: nowrap; - inline-size: 130px; /* fixed column width */ - min-inline-size: 130px; - max-inline-size: 130px; - text-align: end; - overflow: hidden; - text-overflow: ellipsis; - } - - td.column-date .modified-column-content { - display: block; - text-align: start; - } - - &.truncate-row-values { - td.column-name > .snippet-name, - td.column-desc .snippet-description-content { - display: block; - overflow: hidden; - text-overflow: ellipsis; - } - - td.column-name > .snippet-name { - max-inline-size: min(15rem, 30vw); - } - - td.column-desc .snippet-description-content { - max-inline-size: min(25rem, 45vw); - overflow: clip; - } - } -} - -.snippets-table-toolbar { - display: flex; - justify-content: space-between; - align-items: center; - margin: 12px 0; - - .subsubsub { - display: flex; - gap: 5px; - margin: 0; - padding: 0; - } -} - -.tablenav { - margin: 20px 0; - display: flex; - align-items: center; -} - -/* WP 7.0 changes the default admin link colour to the modern blue. Pin the classic accent on active rows (names and - row-action links) so links keep their original colour; inactive rows are handled by the muted link-colors mixin, - and the trash button keeps its dark red from the `.delete` rules. */ -.wp-list-table .active-snippet a { - color: theme.$accent; - - &:hover, - &:focus { - color: theme.$accent-hover; - } -} - -/* Status filter links (All | Active | Inactive | …): WP 7.0 recolours these with the modern blue. Restore the classic - accent for non-current links; the current one keeps its own colour. */ -/* stylelint-disable-next-line no-descending-specificity -- unrelated element; ordering is fine. */ -.subsubsub a:not(.current) { - color: theme.$accent; - - &:hover, - &:focus { - color: theme.$accent-hover; - } -} - -.wp-core-ui .button.clear-filters { - vertical-align: baseline; -} - -.snippet-type-description { - border-block-end: 1px solid #ccc; - margin: 0; - padding-block: 1em; - padding-inline: 0; -} - -.snippets-search-area, -.tablenav .displaying-num { - margin-inline-start: auto; -} - -.snippets-search-area { - display: flex; - align-items: center; - gap: 12px; - - .search-box { - float: none; - margin: 0; - - input[type='search'] { - inline-size: 260px; - min-block-size: 32px; - } - } -} - -@media (width <= 782px) { - p.search-box { - float: inline-start; - position: initial; - margin-block: 1em 0; - margin-inline: 0; - block-size: auto; - } -} +@use 'list-table/layout'; +@use 'list-table/navigation'; +@use 'list-table/pagination'; +@use 'list-table/responsive'; diff --git a/src/css/common/_modal.scss b/src/css/common/_modal.scss index fad7a3835..3a90d67f8 100644 --- a/src/css/common/_modal.scss +++ b/src/css/common/_modal.scss @@ -55,15 +55,15 @@ .components-modal__frame.code-snippets-preview-modal { min-inline-size: 520px; min-block-size: 240px; - max-inline-size: 80vw; - max-block-size: 80vh; + inline-size: min(900px, 90vw); + max-block-size: 85vh; @media (width <= 600px) { min-inline-size: 90vw; } - .components-modal__content { - overflow: auto; + .components-modal__header { + border-block-end-color: #ddd; } // The badge lives in the modal content because the minimum-supported @@ -75,12 +75,89 @@ inset-inline-end: 56px; } - // Shown when the code editor is unavailable (syntax highlighting preference - // off): the raw read-only textarea is the functional fallback. - textarea { - inline-size: 100%; - min-block-size: 360px; - resize: none; - font-family: monospace; + // Header and footer stay pinned; the CodeMirror editor is the only + // scroll region, scrolling long code both vertically and horizontally. + .components-modal__content { + display: flex; + flex-flow: column; + min-block-size: 0; + padding: 0; + overflow: hidden; + } + + // The modal component wraps its children in an unstyled focus container; + // it must join the flex chain or the editor grows to its content height. + .components-modal__header + div { + display: flex; + flex-flow: column; + flex: 1; + min-block-size: 0; + overflow: hidden; + } + + .code-snippets-preview-modal__editor { + display: flex; + flex-flow: column; + flex: 1; + min-block-size: 0; + + .CodeMirror { + flex: 1; + min-block-size: 0; + block-size: 100%; + } + + textarea { + flex: 1; + min-block-size: 0; + inline-size: 100%; + resize: none; + font-family: monospace; + } + } + + .code-snippets-preview-modal__footer { + display: flex; + align-items: center; + justify-content: space-between; + flex-shrink: 0; + gap: 16px; + padding: 11px 19px; + border-block-start: 1px solid #e2e2e4; + background: #f6f7f7; + } + + .code-snippets-preview-modal__priority { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + color: #646970; + + .code-snippets-preview-modal__priority-value { + font-weight: 600; + color: #2c3337; + } + } + + .code-snippets-preview-modal__buttons { + display: flex; + align-items: center; + gap: 8px; + + .button { + border-radius: 5px; + font-size: 14px; + } + + .button-link.code-snippets-preview-modal__trash { + color: #b32d2e; + text-decoration: none; + + &:hover, + &:focus { + color: #d63638; + } + } } } diff --git a/src/css/common/_page-header.scss b/src/css/common/_page-header.scss new file mode 100644 index 000000000..81a8c10e0 --- /dev/null +++ b/src/css/common/_page-header.scss @@ -0,0 +1,45 @@ +@use 'theme'; + +// Contextual page header row shared by the plugin admin screens: the page +// title on the inline-start side and the primary page action, if any, +// aligned to the inline-end side of the same row. +.snippets-page-header { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 16px 24px; + margin-block: 8px 16px; + + h1, + h2 { + margin: 0; + padding: 0; + font-size: 32px; + font-weight: 510; + line-height: 1.25; + color: #000; + } +} + +.wrap .snippets-page-header .button.button-primary { + padding: 12px; + min-block-size: 0; + border-radius: 5px; + line-height: 1; + font-size: 14px; + font-weight: 700; +} + +// One-line supporting description shown directly beneath the page title. +.snippets-page-description { + margin-block: 0 16px; + font-size: 14px; + line-height: 1.5; + color: #646970; + + a { + color: theme.$accent; + text-decoration: underline; + } +} diff --git a/src/css/common/_subnav.scss b/src/css/common/_subnav.scss index 08ea58f78..088efd410 100644 --- a/src/css/common/_subnav.scss +++ b/src/css/common/_subnav.scss @@ -1,10 +1,16 @@ -// Snippet-type navigation styled to match the main plugin toolbar: a white -// bar with tab links showing an icon beside black text, highlighting blue -// with a blue bottom border on hover and for the active type. The create -// button sits at the far end of the bar. +// Snippet-type navigation: a boxed segmented control on a white band, with +// each tab separated by a vertical rule and the active tab shown as a +// filled tile. .snippet-type-nav { + // The tab band scrolls sideways instead of wrapping at any width. + overflow-x: auto; + scrollbar-width: thin; background: #fff; - border-block-end: 1px solid #c3c4c7; + + // The toolbar band above already draws the 1px separator, so only a + // bottom border here — a top border would double the line weight. + border-block-end: 1px solid #e2e2e4; + min-block-size: 53px; // Bleed across the full admin content width, flush against the plugin // toolbar above: counteract the .wrap top margin (10px) plus its 2px / @@ -16,16 +22,32 @@ // Core admin switches to 10px #wpcontent padding, 0/12px .wrap margins // below 782px; mirror them so the bar stays flush without overflowing - // the viewport. + // the viewport. The tabs scroll horizontally on a single row rather than + // stacking, with the scrollbar chrome hidden. @media screen and (width <= 782px) { margin-inline-start: -10px; inline-size: calc(100% + 22px); + overflow-x: auto; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + + ul { + flex-wrap: nowrap; + } + + .snippet-type-link { + white-space: nowrap; + } } ul { display: flex; align-items: stretch; - flex-wrap: wrap; + flex-wrap: nowrap; + min-block-size: 52px; margin: 0; padding: 0; list-style: none; @@ -38,11 +60,9 @@ button.snippet-type-link { background: none; - border-inline: none; - border-block-start: none; + border-block: none; + border-inline-start: none; font-family: inherit; - font-size: 15px; - font-weight: 600; } .snippet-type-icon { @@ -54,19 +74,25 @@ .snippet-type-link { display: flex; align-items: center; - gap: 10px; - padding: 18px 20px 14px; + justify-content: center; + gap: 8px; + min-inline-size: 100px; + padding-block: 0; + padding-inline: 24px; cursor: pointer; - - // Overlap the nav's black bottom border so the blue active/hover - // border paints directly on top of it with no gap between the two. - margin-block-end: -1px; - font-size: 15px; + font-size: 14px; font-weight: 600; - color: #1d2327; + line-height: 1.5; + color: #646970; text-decoration: none; box-sizing: border-box; - border-block-end: 4px solid transparent; + border-inline-end: 1px solid #e2e2e4; + + // The text-only "All Snippets" tab uses a slightly wider gap between + // its label and count than tabs that lead with a badge or icon. + &.all-type-link { + gap: 12px; + } svg { display: block; @@ -76,10 +102,18 @@ &:hover, &:focus, - &:active, + &:active { + color: #2c3337; + } + &.active-type { + background: #f0f0f1; + border-color: #f0f0f1; color: #2271b1; - border-block-end-color: currentcolor; + + // Children paint over the parent nav's border, so this recolours + // the 1px band separator beneath the active tab to match its fill. + box-shadow: 0 1px 0 0 #f0f0f1; } // Suppress the persistent focus ring left behind after a mouse click; @@ -100,19 +134,51 @@ } } - .snippet-view-toggle-nav-item { - margin-inline-start: auto; - align-items: center; - } +} + +// Card/table view toggle: a two-icon segmented pair of square tiles with +// the active view filled in the accent colour. +.snippet-view-toggle { + display: flex; + align-items: center; + gap: 4px; - .create-snippet-nav-item { - margin-inline-start: 24px; - padding-inline-end: 16px; + .snippet-view-toggle-option { + display: flex; align-items: center; + justify-content: center; + inline-size: 38px; + block-size: 38px; + box-sizing: border-box; + padding: 0; + margin: 0; + background: #f0f0f1; + color: #646970; + border: none; + border-radius: 4.5px; + cursor: pointer; + + svg { + display: block; + } + + &:hover, + &:focus { + color: #2c3337; + } + + &.active-view { + background: #2271b1; + color: #fff; + + &:hover, + &:focus { + color: #fff; + } + } } } - // Slot that adopts the WordPress Screen Options / Help tabs so they appear // directly below the snippet-type nav instead of above the page content. .snippets-screen-meta-slot { @@ -128,29 +194,46 @@ } } - -// Count pill shown beside a subnav tab's label (e.g. total on the "All" tab). +// Item count shown after a subnav tab's label, as muted text. .snippet-type-nav .subnav-count { - display: inline-flex; - align-items: center; - justify-content: center; - min-inline-size: 20px; - block-size: 20px; - padding-inline: 7px; - border-radius: 999px; - background: #2271b1; - color: #fff; - font-size: 11px; + font-size: 14px; font-weight: 600; - line-height: 1; - box-sizing: border-box; + line-height: 1.5; + color: #646970; } -.snippet-type-nav .snippet-type-link:not(.active-type) .subnav-count { - background: #dbebf7; +// The active tab's count adopts the accent alongside its label. +.snippet-type-nav .snippet-type-link.active-type .subnav-count { color: #2271b1; } +// The short "All" form of the all-snippets label only replaces "All Snippets" +// once the bar collapses; the full form is shown at every wider width. +.snippet-type-name-short { + display: none; +} + +// Collapse at mid-width and below (through mobile): drop the type names so each +// tab shows just its badge and count, and shorten "All Snippets" to "All". Only +// the badged manage nav owns a .snippet-type-name; the text-only import/settings +// subnav tabs are untouched. Below 782px the bar still scrolls sideways (above), +// now with the compact collapsed tabs. +@media (width <= 1210px) { + .snippet-type-nav .snippet-type-link:not(.all-type-link) .snippet-type-name { + display: none; + } + + .snippet-type-nav .all-type-link .snippet-type-name-full { + display: none; + } + + .snippet-type-nav .all-type-link .snippet-type-name-short { + display: inline; + } +} + +// When an admin notice pushes the page content down, restore the gap and top +// border above the subnav bar. .notice + .wrap .snippet-type-nav { margin-block-start: 20px; border-block-start: 1px solid #c3c4c7; diff --git a/src/css/common/_switch.scss b/src/css/common/_switch.scss index 3d47ad39b..9823e71a4 100644 --- a/src/css/common/_switch.scss +++ b/src/css/common/_switch.scss @@ -1,7 +1,5 @@ @use 'theme'; -$off-color: #789; - .snippet-execution-button, .snippet-activation-switch, input[type='checkbox'].switch { @@ -9,17 +7,19 @@ input[type='checkbox'].switch { position: relative; } +// Activation switch: an accent-coloured 36x19 pill shared by the list-table +// activate column, snippet cards and settings toggles. .snippet-activation-switch, input[type='checkbox'].switch { appearance: none; outline: 0; cursor: pointer; margin: 0; - inline-size: 32px; + inline-size: 36px; block-size: 19px; border-radius: 34px; text-align: start; - border: 1px solid $off-color; + border: 1px solid theme.$accent; box-sizing: border-box; &::before { @@ -28,7 +28,7 @@ input[type='checkbox'].switch { block-size: 13px; inline-size: 13px; display: inline-block; - background-color: $off-color; + background-color: theme.$accent; border-radius: 50%; margin: 2px; @@ -40,11 +40,12 @@ input[type='checkbox'].switch { .active-snippet .snippet-activation-switch, input[type='checkbox'].switch:checked { - background-color: #0073aa; + background-color: theme.$accent; + // Travel distance: pill width minus the knob, its margins and the borders. &::before { background-color: white; - transform: translateX(calc(100% * var(--cs-direction-multiplier))); + transform: translateX(calc((100% + 4px) * var(--cs-direction-multiplier))); } } @@ -112,14 +113,3 @@ a.snippet-condition-count { } } } - -.snippet-view-toggle { - display: flex; - align-items: center; - gap: 8px; - padding-inline-end: 16px; - - input[type='checkbox'].switch { - display: inline-block; - } -} diff --git a/src/css/common/_theme.scss b/src/css/common/_theme.scss index 9f376cede..a836dce51 100644 --- a/src/css/common/_theme.scss +++ b/src/css/common/_theme.scss @@ -9,13 +9,13 @@ $brand-facebook: #3b5998; $cloud: #00bcd4; $cloud-update: #ff9800; -// WordPress classic admin control palette. WP 7.0's "modern" redesign restyles native form -// controls; these restore the classic metrics our admin UI is designed around (see -// common/_wp-admin.scss). +// Plugin admin control tokens, applied to native form controls on all plugin +// screens regardless of WordPress version (see common/_wp-admin.scss). $accent-hover: #0a4b78; -$control-border: #8c8f94; +$control-border: #c3c4c7; $control-text: #2c3338; -$control-height: 30px; +$control-height: 38px; +$control-radius: 5px; /* format: background-color [color] */ $badges: ( @@ -24,7 +24,7 @@ $badges: ( php: $accent, html: #cd4510, css: #9b59b6, - js: #ffeb3b #1c1f20, + js: #f7d67a #1c1f20, cond: #22826f, core: #61c5cb, pro: #f7e8e3 #df9279, diff --git a/src/css/common/_toolbar.scss b/src/css/common/_toolbar.scss index b05cd0699..7b1abaa5d 100644 --- a/src/css/common/_toolbar.scss +++ b/src/css/common/_toolbar.scss @@ -1,6 +1,6 @@ @use 'upsell'; -$toolbar-block-size: 150px; +$toolbar-block-size: 136px; $wpcontent-inline-start-indent: 20px; #wpbody { @@ -28,8 +28,11 @@ $wpcontent-inline-start-indent: 20px; .code-snippets-toolbar-upper { justify-content: space-between; - block-size: 77px; + block-size: 76px; + box-sizing: border-box; + padding-block: 16px; padding-inline: 24px; + border-block-end: 1px solid rgb(195 196 199 / 50%); display: flex; align-items: center; @@ -62,37 +65,102 @@ $wpcontent-inline-start-indent: 20px; } .code-snippets-toolbar-lower { - border-block: 1px solid #c3c4c7; + border-block-end: 1px solid #e2e2e4; + block-size: 60px; + box-sizing: border-box; + + nav, + ul { + block-size: 100%; + } ul { display: flex; - align-items: end; + align-items: stretch; + } + + li { + display: flex; } + // Items fill the 60px band; the active tab is indicated by accent colour on + // its icon and label, with no underline. li a { display: flex; margin: 0; - padding: 24px 24px 20px; - color: inherit; + block-size: 100%; + padding-block: 0; + padding-inline: 24px; + color: #2c3337; gap: 12px; align-items: center; font-size: 16px; + font-weight: 600; + letter-spacing: -0.16px; text-decoration: none; - font-weight: bold; box-sizing: border-box; - transition: border unset; - border-block-end: 4px solid transparent; cursor: pointer; + svg { + inline-size: 26px; + block-size: 26px; + } + &.active-link, &:hover, &:focus, &:active { color: #2271b1; - border-block-end-color: currentcolor; } } - li:last-of-type { + li:not(.toolbar-end-item) + li.toolbar-end-item { margin-inline-start: auto; } + + // Narrow widths: centre each item's label directly beneath its icon and + // shrink the type so the nav still fits on one row. The pro chip becomes a + // small badge pinned to the item's top corner, out of the flow so it never + // shifts the icon off-centre from its label. Below 782px the row scrolls + // sideways (see the mobile block below) rather than wrapping. + @media (width <= 1140px) { + li a { + position: relative; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 4px; + padding-inline: 14px; + font-size: 13px; + text-align: center; + + .pro-chip { + position: absolute; + inset-block-start: 7px; + inset-inline-end: 4px; + padding-block: 1px; + padding-inline: 5px; + font-size: 8px; + line-height: normal; + } + } + } + + // Mobile: the primary navigation scrolls horizontally on its band rather + // than wrapping, with the scrollbar chrome hidden. + @media screen and (width <= 782px) { + overflow-x: auto; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + + ul { + flex-wrap: nowrap; + } + + li a { + white-space: nowrap; + } + } } .code-snippets-toolbar-upper li a.active-link, @@ -105,16 +173,18 @@ $wpcontent-inline-start-indent: 20px; .code-snippets-toolbar-lower .pro-chip, .nav-tab .pro-chip, .snippet-type-link .pro-chip { - color: #d46f4d; + color: #2271b1; + background: #eff5f9; + border: 1px solid rgb(34 113 177 / 10%); text-transform: uppercase; - border: 2px solid currentcolor; border-radius: 999px; display: flex; justify-content: center; align-items: center; box-sizing: border-box; - padding: 3px 9px; - font-weight: 600; + padding: 5px 12px; + font-weight: 700; + line-height: 1; } @@ -125,7 +195,6 @@ $wpcontent-inline-start-indent: 20px; .nav-tab .pro-chip, .snippet-type-link .pro-chip { font-size: 10px; - padding: 0 6px; } .code-snippets-return-link { diff --git a/src/css/common/_wp-admin.scss b/src/css/common/_wp-admin.scss index f511cef57..b94f57a6d 100644 --- a/src/css/common/_wp-admin.scss +++ b/src/css/common/_wp-admin.scss @@ -1,18 +1,16 @@ @use 'theme'; /** - * WordPress 7.0 admin compatibility. + * Native form control styling. * - * WP 7.0 ships the "modern" admin redesign, which enlarges native form controls - * (40px tall vs. 30/32px), reduces their corner radius (2px vs. 3/4px) and recolours - * secondary buttons with a transparent fill and the modern accent (#3858E9). Our admin - * UI was designed against the classic control metrics and the brand accent (#2271b1), - * so the redesign throws our spacing and colours out of alignment. - * - * These rules restore the classic control metrics for native elements inside our admin - * pages so the layout renders consistently on both WP 6.9 and WP 7.0. They deliberately - * leave the admin menu, admin bar and `@wordpress/components` React widgets untouched — - * only native inputs/buttons rendered inside our own markup are normalised. + * WordPress core ships different control metrics between versions (30/32px classic, + * 40px with a 2px radius in the WP 7.0 "modern" redesign) and recolours secondary + * buttons with the modern accent (#3858E9). These rules apply the plugin's own control + * tokens — 38px height, 5px radius, #c3c4c7 borders and the brand accent (#2271b1) — + * to native elements inside our admin pages so the layout renders consistently across + * WP versions. They deliberately leave the admin menu, admin bar and + * `@wordpress/components` React widgets untouched — only native inputs/buttons + * rendered inside our own markup are normalised. */ // Scope to our admin page content. We intentionally keep this to a single `.wrap` class so @@ -21,7 +19,7 @@ // win on equal specificity. This lets bespoke controls (conditions button, row-action links, // priority input, etc.) keep their intended styling without per-element exclusions here. .wrap { - // Native single-line text controls and selects: restore 30px height + classic border. + // Native single-line text controls and selects: token height + border. // `@wordpress/components` inputs/selects (`.components-*`) keep their own design — they // are excluded so React widgets such as the tags token field are left untouched. // `.snippet-priority` (the borderless list-table priority field) is excluded so it keeps @@ -33,13 +31,13 @@ input[type='url']:not([class*='components-']), input[type='password']:not([class*='components-']), input[type='tel']:not([class*='components-']) { - // `min-block-size` (not a fixed `block-size`) so controls floor at 30px but can still - // flex-stretch where the layout calls for it (e.g. the 54px cloud search bar). + // `min-block-size` (not a fixed `block-size`) so controls floor at the token height but + // can still flex-stretch where the layout calls for it (e.g. the 54px cloud search bar). min-block-size: theme.$control-height; padding-block: 0; padding-inline: 8px; border: 1px solid theme.$control-border; - border-radius: 4px; + border-radius: theme.$control-radius; line-height: 2; color: theme.$control-text; font-size: 14px; @@ -59,10 +57,10 @@ padding-block: 0; padding-inline: 8px 24px; border: 1px solid theme.$control-border; - border-radius: 3px; + border-radius: theme.$control-radius; - // WP 7.0 leaves a tall (≈38px) line-height on selects, pushing the value off-centre in - // the 30px control. Pin it so the text stays vertically centred. + // WP 7.0 leaves a tall (≈38px) line-height on selects, pushing the value off-centre. + // Pin it so the text stays vertically centred within the token height. line-height: 2; color: theme.$control-text; font-size: 14px; @@ -74,17 +72,18 @@ } } - // Secondary buttons: restore classic geometry, the grey fill and the brand accent - // border/text in place of WP 7.0's transparent + modern-blue treatment. `.button-primary` - // is excluded so its solid fill below is not overwritten. + // Secondary buttons: token geometry, the grey fill and the brand accent border/text in + // place of WP 7.0's transparent + modern-blue treatment. `.button-primary` is excluded + // so its solid fill below is not overwritten. The line-height centres text vertically + // in anchor-based buttons, which lack a button element's automatic centring. .button:not(.button-primary, .button-link), .button-secondary { min-block-size: theme.$control-height; padding-block: 0; padding-inline: 12px; - border-radius: 3px; - line-height: 2.3077; - font-size: 13px; + border-radius: theme.$control-radius; + line-height: 2.5715; + font-size: 14px; font-weight: 400; background: #f6f7f7; border-color: theme.$accent; @@ -104,15 +103,17 @@ } // Primary buttons: WP 7.0 recolours these with the modern accent and drops the solid - // fill. Restore the classic solid brand-accent fill with white text. - .button-primary { + // fill. Restore the classic solid brand-accent fill with white text. The `:not` + // matches the secondary rule's specificity so a `.button-small.button-primary` + // keeps the token height instead of collapsing to the 26px small size. + .button-primary:not(.button-link) { min-block-size: theme.$control-height; padding-block: 0; padding-inline: 12px; - border-radius: 3px; - line-height: 2.3077; - font-size: 13px; - font-weight: 400; + border-radius: theme.$control-radius; + line-height: 2.5715; + font-size: 14px; + font-weight: 700; background: theme.$accent; border-color: theme.$accent; color: #fff; diff --git a/src/css/common/list-table/_layout.scss b/src/css/common/list-table/_layout.scss new file mode 100644 index 000000000..8141219f2 --- /dev/null +++ b/src/css/common/list-table/_layout.scss @@ -0,0 +1,264 @@ +@use '../theme'; + +.column-name { + .extra-icons { + float: inline-end; + display: flex; + flex-wrap: wrap; + gap: 5px; + } + + .dashicons-lock { + color: #646970; + opacity: 0.7; + + &:hover { + opacity: 1; + } + } +} + +.active-snippet { + td, th { + background-color: rgba(#78c8e6, 0.06); + } + + th.check-column { + border-inline-start: 2px solid #2ea2cc; + } + + .column-name > .snippet-name { + font-weight: 600; + } +} + +.inactive-snippet { + @include theme.link-colors(#579); +} + +.paging-input { + display: inline-flex; + align-items: center; + gap: 2px; +} + +.wp-list-table { + td.column-id { + text-align: center; + } + + tr { + background: #fff; + } + + ol, ul { + margin-block: 0 1.5em; + margin-inline: 1.5em 0; + } + + ul { + list-style: disc; + } + + .sortable-column-title { + display: flex; + } + + th.sortable .list-table-sort-button, + th.sorted .list-table-sort-button { + display: flex; + flex-direction: row; + align-items: center; + inline-size: 100%; + margin: 0; + border: none; + background: none; + font: inherit; + color: #2271b1; + cursor: pointer; + text-align: start; + overflow: hidden; + padding: 8px; + } + + th.sortable .list-table-sort-button:focus-visible, + th.sorted .list-table-sort-button:focus-visible { + outline: 2px solid #2271b1; + outline-offset: 2px; + border-radius: 2px; + } + + .row-actions { + color: #646970; + position: relative; + inset-inline-start: 0; + + // Row-action buttons render as plain inline links. On WP 7.0 the generic `.button` + // compatibility styling would otherwise give them a fill, border, radius and fixed + // height, so fully neutralise it here (this rule is emitted after the wp-admin layer + // at equal specificity, so it wins). Non-delete links inherit the accent text colour. + .button-link { + block-size: auto; + min-block-size: 0; + padding: 0; + border: 0; + border-radius: 0; + background: none; + box-shadow: none; + line-height: inherit; + font-weight: 400; + + &:hover:not(:disabled), + &:focus:not(:disabled) { + background: none; + border: 0; + box-shadow: none; + } + + &.delete { + color: #b32d2e; + } + + &.snippet-cloud-update { + color: #ff851b; + } + } + + .snippet-row-action-feedback, + .snippet-row-action-error { + display: inline-flex; + align-items: center; + gap: .5em; + + .components-spinner { + margin: 0; + inline-size: 1em; + block-size: 1em; + } + + } + + .snippet-row-action-error { + color: #b32d2e; + + } + + .delete.disabled { + color: #a7aaad; + cursor: not-allowed; + pointer-events: none; + } + } + + .column-activate { + padding-inline-end: 0 !important; + } + + .clear-filters { + vertical-align: middle; + } + + tfoot th.check-column { + padding: 13px 0 0 3px; + } + + thead th.check-column, + tfoot th.check-column, + .inactive-snippet th.check-column { + padding-inline-start: 5px; + } + + .active-snippet, .inactive-snippet { + td, th { + padding: 10px 9px; + box-shadow: inset 0 -1px 0 rgb(0 0 0 / 10%); + } + } + + tr.active-snippet + tr.inactive-snippet th, + tr.active-snippet + tr.inactive-snippet td { + border-block-start: 1px solid rgb(0 0 0 / 3%); + box-shadow: inset 0 1px 0 rgb(0 0 0 / 2%), inset 0 -1px 0 #e1e1e1; + } + + .delete { + color: #b32d2e; + } + + a.delete:not(.disabled) { + &:hover, &:focus, &:active { + border-block-end: 1px solid #f00; + color: #f00; + } + } + + td.column-date, th.column-date { + white-space: nowrap; + inline-size: 130px; /* fixed column width */ + min-inline-size: 130px; + max-inline-size: 130px; + text-align: end; + overflow: hidden; + text-overflow: ellipsis; + } + + td.column-date .modified-column-content { + display: block; + text-align: start; + } + + // Snippet names always stay on a single line, truncating gracefully; + // the full name is exposed through the title attribute. + td.column-name > .snippet-name { + display: block; + max-inline-size: min(15rem, 30vw); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + &.truncate-row-values td.column-desc .snippet-description-content { + display: block; + max-inline-size: min(25rem, 45vw); + overflow: clip; + text-overflow: ellipsis; + } +} + +.snippets-table-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + + // The preceding page header owns the 16px gap above; a `wp-header-end`