diff --git a/packages/craftcms-cp/scripts/generate-colors.js b/packages/craftcms-cp/scripts/generate-colors.js index 5533c2423f7..2f488a554c1 100644 --- a/packages/craftcms-cp/scripts/generate-colors.js +++ b/packages/craftcms-cp/scripts/generate-colors.js @@ -55,9 +55,9 @@ function lightScale(color) { borderQuiet: 'var(--color-gray-800)', borderNormal: 'var(--color-gray-800)', borderLoud: 'var(--color-gray-800)', - onQuiet: 'var(--color-gray-100)', - onNormal: 'var(--color-gray-100)', - onLoud: 'var(--color-gray-100)', + onQuiet: 'var(--color-gray-50)', + onNormal: 'var(--color-gray-50)', + onLoud: 'var(--color-gray-50)', }; default: return { @@ -136,7 +136,7 @@ function buildTokens(colors, scaleFn) { } function buildStyleBlock(color) { - return `.c-colorable--${color}, + return `.cp-colorable--${color}, [data-color='${color}'] { --c-color-fill-quiet: var(--c-color-${color}-fill-quiet); --c-color-border-quiet: var(--c-color-${color}-border-quiet); @@ -161,7 +161,7 @@ ${buildTokens(colors, lightScale)} ${buildTokens(colors, darkScale)} } -.c-colorable, +.cp-colorable, [data-color] { --c-color-fill-quiet: var(--c-color-neutral-fill-quiet); --c-color-fill-normal: var(--c-color-neutral-fill-normal); diff --git a/packages/craftcms-cp/src/components/button/button.styles.ts b/packages/craftcms-cp/src/components/button/button.styles.ts index 52ec9d5c69c..7c6995207e4 100644 --- a/packages/craftcms-cp/src/components/button/button.styles.ts +++ b/packages/craftcms-cp/src/components/button/button.styles.ts @@ -36,7 +36,7 @@ export default css` :host(:hover) { background-color: color-mix( in oklab, - var(--c-color-fill-loud, var(--c-button-default-fill)), + var(--c-color-fill-loud, var(--c-color-neutral-fill-loud)), var(--c-color-mix-hover) ); color: var(--c-color-on-loud); @@ -123,7 +123,7 @@ export default css` :host([appearance~='plain']:hover) { background-color: color-mix( in oklab, - var(--c-color-fill-quiet, var(--c-button-default-fill)), + var(--c-color-fill-quiet, var(--c-color-neutral-fill-quiet)), var(--c-color-mix-hover) ); color: var(--c-color-on-quiet); diff --git a/packages/craftcms-cp/src/components/chip/chip.styles.ts b/packages/craftcms-cp/src/components/chip/chip.styles.ts index 0b23608dd77..7e44dfd6faa 100644 --- a/packages/craftcms-cp/src/components/chip/chip.styles.ts +++ b/packages/craftcms-cp/src/components/chip/chip.styles.ts @@ -37,13 +37,13 @@ export default css` .cp-chip[size='small'], .cp-chip--small { - padding-block: 0; + padding-block: var(--c-spacing-xs); min-height: var(--c-size-control-sm); } .cp-chip[size='medium'], .cp-chip--medium { - padding-block: 0; + padding-block: var(--c-spacing-sm); min-height: var(--c-size-control-md); } diff --git a/packages/craftcms-cp/src/components/tooltip/tooltip.stories.ts b/packages/craftcms-cp/src/components/tooltip/tooltip.stories.ts index 600bfe471f1..556f01f38a8 100644 --- a/packages/craftcms-cp/src/components/tooltip/tooltip.stories.ts +++ b/packages/craftcms-cp/src/components/tooltip/tooltip.stories.ts @@ -24,11 +24,11 @@ const meta = { } return html` - ${args.content}${args.content} Hover me `; diff --git a/packages/craftcms-cp/src/components/tooltip/tooltip.ts b/packages/craftcms-cp/src/components/tooltip/tooltip.ts index c25fe343fd0..ae10108caac 100644 --- a/packages/craftcms-cp/src/components/tooltip/tooltip.ts +++ b/packages/craftcms-cp/src/components/tooltip/tooltip.ts @@ -12,37 +12,30 @@ export default class CraftTooltip extends WaTooltip { return [ WaTooltip.styles, css` - wa-popup { - --wa-z-index-tooltip: var(--c-tooltip-z-index, 1000); - --wa-tooltip-background-color: var( - --c-tooltip-fill, - var(--c-surface-overlay) - ); - --wa-tooltip-border-color: var( - --c-tooltip-border, - var(--c-color-neutral-border-quiet) - ); - --wa-tooltip-content-color: var(--c-tooltip-text, currentColor); + :host { + --wa-tooltip-background-color: var(--c-color-black-fill-loud); + --wa-tooltip-border-color: var(--c-color-black-border-loud); + --wa-tooltip-content-color: var(--c-color-black-on-loud); --wa-tooltip-padding: var( --c-tooltip-padding, calc(4rem / 16) calc(8rem / 16) ); --wa-tooltip-arrow-size: var(--c-tooltip-arrow-size, 5px); --wa-tooltip-font-family: inherit; - --wa-tooltip-font-size: var( - --c-tooltip-font-size, - var(--c-text-base) - ); - --wa-tooltip-font-weight: var(--c-tooltip-font-weight, 400); - --wa-tooltip-line-height: var(--c-tooltip-line-height, 1.3); - --wa-tooltip-border-radius: var( - --c-tooltip-border-radius, - var(--c-radius-sm) - ); - font-weight: 400; - color: var(--c-tooltip-text, currentColor); + --wa-tooltip-font-size: var(--c-text-base); + --wa-tooltip-font-weight: 400; + --wa-tooltip-line-height: 1.3; + --wa-tooltip-border-radius: var(--c-radius-sm); + } + + &::part(base) { box-shadow: var(--c-shadow-md); } + + .body { + color: var(--wa-tooltip-content-color); + font-weight: var(--wa-tooltip-font-weight); + } `, ]; } diff --git a/packages/craftcms-cp/src/styles/shared/base.css b/packages/craftcms-cp/src/styles/shared/base.css index fea2a124570..fdf8d305b3b 100644 --- a/packages/craftcms-cp/src/styles/shared/base.css +++ b/packages/craftcms-cp/src/styles/shared/base.css @@ -40,12 +40,17 @@ ul { list-style: none; } -.code { - font-size: 0.9em; +.cp-code { + font-size: 0.75em; + font-family: var(--c-font-mono); display: inline-flex; padding: 0 var(--c-spacing-sm); - border: 1px solid rgba(0, 0, 0, 0.2); - background-color: rgba(0, 0, 0, 0.05); + color: var(--c-color-on-quiet); + border: 1px solid var(--c-color-border-quiet); + background-color: color-mix( + var(--c-color-fill-loud) 10%, + var(--c-color-fill-quiet) + ); border-radius: var(--c-radius-sm); } diff --git a/packages/craftcms-cp/src/styles/shared/colorable.css b/packages/craftcms-cp/src/styles/shared/colorable.css index 7e82e67bb61..e703dc37a71 100644 --- a/packages/craftcms-cp/src/styles/shared/colorable.css +++ b/packages/craftcms-cp/src/styles/shared/colorable.css @@ -1,4 +1,4 @@ -/* Auto-generated by scripts/generate-colors.ts — do not edit manually */ +/* Auto-generated by scripts/generate-colors.js — do not edit manually */ :root { /* red */ @@ -215,8 +215,8 @@ --c-color-black-fill-normal: var(--color-gray-900); --c-color-black-fill-loud: var(--color-gray-900); --c-color-black-border-quiet: var(--color-gray-800); - --c-color-black-border-normal: undefined; - --c-color-black-border-loud: undefined; + --c-color-black-border-normal: var(--color-gray-800); + --c-color-black-border-loud: var(--color-gray-800); --c-color-black-on-quiet: var(--color-gray-100); --c-color-black-on-normal: var(--color-gray-100); --c-color-black-on-loud: var(--color-gray-100); @@ -444,24 +444,24 @@ --c-color-black-on-loud: var(--color-gray-300); } -.c-colorable, +.cp-colorable, [data-color] { --c-color-fill-quiet: var(--c-color-neutral-fill-quiet); - --c-color-fill-normal: var(--c-color-neutral-fill-quiet); - --c-color-fill-loud: var(--c-color-neutral-fill-quiet); + --c-color-fill-normal: var(--c-color-neutral-fill-normal); + --c-color-fill-loud: var(--c-color-neutral-fill-loud); --c-color-border-quiet: var(--c-color-neutral-border-quiet); - --c-color-border-normal: var(--c-color-neutral-border-quiet); - --c-color-border-loud: var(--c-color-neutral-border-quiet); + --c-color-border-normal: var(--c-color-neutral-border-normal); + --c-color-border-loud: var(--c-color-neutral-border-loud); --c-color-on-quiet: var(--c-color-neutral-on-quiet); - --c-color-on-normal: var(--c-color-neutral-on-quiet); - --c-color-on-loud: var(--c-color-neutral-on-quiet); + --c-color-on-normal: var(--c-color-neutral-on-normal); + --c-color-on-loud: var(--c-color-neutral-on-loud); background-color: var(--c-color-fill-quiet); border-color: var(--c-color-border-quiet); color: var(--c-color-on-quiet); } -.c-colorable--red, +.cp-colorable--red, [data-color='red'] { --c-color-fill-quiet: var(--c-color-red-fill-quiet); --c-color-border-quiet: var(--c-color-red-border-quiet); @@ -473,7 +473,7 @@ --c-color-border-loud: var(--c-color-red-border-loud); --c-color-on-loud: var(--c-color-red-on-loud); } -.c-colorable--orange, +.cp-colorable--orange, [data-color='orange'] { --c-color-fill-quiet: var(--c-color-orange-fill-quiet); --c-color-border-quiet: var(--c-color-orange-border-quiet); @@ -485,7 +485,7 @@ --c-color-border-loud: var(--c-color-orange-border-loud); --c-color-on-loud: var(--c-color-orange-on-loud); } -.c-colorable--amber, +.cp-colorable--amber, [data-color='amber'] { --c-color-fill-quiet: var(--c-color-amber-fill-quiet); --c-color-border-quiet: var(--c-color-amber-border-quiet); @@ -497,7 +497,7 @@ --c-color-border-loud: var(--c-color-amber-border-loud); --c-color-on-loud: var(--c-color-amber-on-loud); } -.c-colorable--yellow, +.cp-colorable--yellow, [data-color='yellow'] { --c-color-fill-quiet: var(--c-color-yellow-fill-quiet); --c-color-border-quiet: var(--c-color-yellow-border-quiet); @@ -509,7 +509,7 @@ --c-color-border-loud: var(--c-color-yellow-border-loud); --c-color-on-loud: var(--c-color-yellow-on-loud); } -.c-colorable--lime, +.cp-colorable--lime, [data-color='lime'] { --c-color-fill-quiet: var(--c-color-lime-fill-quiet); --c-color-border-quiet: var(--c-color-lime-border-quiet); @@ -521,7 +521,7 @@ --c-color-border-loud: var(--c-color-lime-border-loud); --c-color-on-loud: var(--c-color-lime-on-loud); } -.c-colorable--green, +.cp-colorable--green, [data-color='green'] { --c-color-fill-quiet: var(--c-color-green-fill-quiet); --c-color-border-quiet: var(--c-color-green-border-quiet); @@ -533,7 +533,7 @@ --c-color-border-loud: var(--c-color-green-border-loud); --c-color-on-loud: var(--c-color-green-on-loud); } -.c-colorable--emerald, +.cp-colorable--emerald, [data-color='emerald'] { --c-color-fill-quiet: var(--c-color-emerald-fill-quiet); --c-color-border-quiet: var(--c-color-emerald-border-quiet); @@ -545,7 +545,7 @@ --c-color-border-loud: var(--c-color-emerald-border-loud); --c-color-on-loud: var(--c-color-emerald-on-loud); } -.c-colorable--teal, +.cp-colorable--teal, [data-color='teal'] { --c-color-fill-quiet: var(--c-color-teal-fill-quiet); --c-color-border-quiet: var(--c-color-teal-border-quiet); @@ -557,7 +557,7 @@ --c-color-border-loud: var(--c-color-teal-border-loud); --c-color-on-loud: var(--c-color-teal-on-loud); } -.c-colorable--cyan, +.cp-colorable--cyan, [data-color='cyan'] { --c-color-fill-quiet: var(--c-color-cyan-fill-quiet); --c-color-border-quiet: var(--c-color-cyan-border-quiet); @@ -569,7 +569,7 @@ --c-color-border-loud: var(--c-color-cyan-border-loud); --c-color-on-loud: var(--c-color-cyan-on-loud); } -.c-colorable--sky, +.cp-colorable--sky, [data-color='sky'] { --c-color-fill-quiet: var(--c-color-sky-fill-quiet); --c-color-border-quiet: var(--c-color-sky-border-quiet); @@ -581,7 +581,7 @@ --c-color-border-loud: var(--c-color-sky-border-loud); --c-color-on-loud: var(--c-color-sky-on-loud); } -.c-colorable--blue, +.cp-colorable--blue, [data-color='blue'] { --c-color-fill-quiet: var(--c-color-blue-fill-quiet); --c-color-border-quiet: var(--c-color-blue-border-quiet); @@ -593,7 +593,7 @@ --c-color-border-loud: var(--c-color-blue-border-loud); --c-color-on-loud: var(--c-color-blue-on-loud); } -.c-colorable--indigo, +.cp-colorable--indigo, [data-color='indigo'] { --c-color-fill-quiet: var(--c-color-indigo-fill-quiet); --c-color-border-quiet: var(--c-color-indigo-border-quiet); @@ -605,7 +605,7 @@ --c-color-border-loud: var(--c-color-indigo-border-loud); --c-color-on-loud: var(--c-color-indigo-on-loud); } -.c-colorable--violet, +.cp-colorable--violet, [data-color='violet'] { --c-color-fill-quiet: var(--c-color-violet-fill-quiet); --c-color-border-quiet: var(--c-color-violet-border-quiet); @@ -617,7 +617,7 @@ --c-color-border-loud: var(--c-color-violet-border-loud); --c-color-on-loud: var(--c-color-violet-on-loud); } -.c-colorable--purple, +.cp-colorable--purple, [data-color='purple'] { --c-color-fill-quiet: var(--c-color-purple-fill-quiet); --c-color-border-quiet: var(--c-color-purple-border-quiet); @@ -629,7 +629,7 @@ --c-color-border-loud: var(--c-color-purple-border-loud); --c-color-on-loud: var(--c-color-purple-on-loud); } -.c-colorable--fuchsia, +.cp-colorable--fuchsia, [data-color='fuchsia'] { --c-color-fill-quiet: var(--c-color-fuchsia-fill-quiet); --c-color-border-quiet: var(--c-color-fuchsia-border-quiet); @@ -641,7 +641,7 @@ --c-color-border-loud: var(--c-color-fuchsia-border-loud); --c-color-on-loud: var(--c-color-fuchsia-on-loud); } -.c-colorable--pink, +.cp-colorable--pink, [data-color='pink'] { --c-color-fill-quiet: var(--c-color-pink-fill-quiet); --c-color-border-quiet: var(--c-color-pink-border-quiet); @@ -653,7 +653,7 @@ --c-color-border-loud: var(--c-color-pink-border-loud); --c-color-on-loud: var(--c-color-pink-on-loud); } -.c-colorable--rose, +.cp-colorable--rose, [data-color='rose'] { --c-color-fill-quiet: var(--c-color-rose-fill-quiet); --c-color-border-quiet: var(--c-color-rose-border-quiet); @@ -665,7 +665,7 @@ --c-color-border-loud: var(--c-color-rose-border-loud); --c-color-on-loud: var(--c-color-rose-on-loud); } -.c-colorable--white, +.cp-colorable--white, [data-color='white'] { --c-color-fill-quiet: var(--c-color-white-fill-quiet); --c-color-border-quiet: var(--c-color-white-border-quiet); @@ -677,7 +677,7 @@ --c-color-border-loud: var(--c-color-white-border-loud); --c-color-on-loud: var(--c-color-white-on-loud); } -.c-colorable--gray, +.cp-colorable--gray, [data-color='gray'] { --c-color-fill-quiet: var(--c-color-gray-fill-quiet); --c-color-border-quiet: var(--c-color-gray-border-quiet); @@ -689,7 +689,7 @@ --c-color-border-loud: var(--c-color-gray-border-loud); --c-color-on-loud: var(--c-color-gray-on-loud); } -.c-colorable--black, +.cp-colorable--black, [data-color='black'] { --c-color-fill-quiet: var(--c-color-black-fill-quiet); --c-color-border-quiet: var(--c-color-black-border-quiet); diff --git a/packages/craftcms-cp/src/styles/shared/tokens.css b/packages/craftcms-cp/src/styles/shared/tokens.css index 39c24cb3697..177e3735436 100644 --- a/packages/craftcms-cp/src/styles/shared/tokens.css +++ b/packages/craftcms-cp/src/styles/shared/tokens.css @@ -283,13 +283,19 @@ /** Web Awesome */ + + /* Colors */ + --wa-color-surface-border: var(--c-color-neutral-border-quiet); + --wa-color-surface-raised: var(--c-surface-raised); + + /* Shadow */ + --wa-shadow-l: var(--c-shadow-lg); + + /* Panels */ --wa-panel-border-style: solid; --wa-panel-border-width: 1px; - --wa-color-surface-border: var(--c-color-neutral-border-quiet); --wa-panel-border-color: var(--c-color-neutral-border-quiet); --wa-panel-border-radius: var(--c-radius-md); - --wa-color-surface-raised: var(--c-surface-raised); - --wa-shadow-l: var(--c-shadow-lg); } [data-theme='dark'] { diff --git a/resources/js/components/form/ComponentSelect/ComponentSelect.vue b/resources/js/components/form/ComponentSelect/ComponentSelect.vue new file mode 100644 index 00000000000..72324fc6be8 --- /dev/null +++ b/resources/js/components/form/ComponentSelect/ComponentSelect.vue @@ -0,0 +1,217 @@ + + + + + diff --git a/resources/js/components/form/ComponentSelect/EntryTypeSelect.vue b/resources/js/components/form/ComponentSelect/EntryTypeSelect.vue new file mode 100644 index 00000000000..6ca2d99a71f --- /dev/null +++ b/resources/js/components/form/ComponentSelect/EntryTypeSelect.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/resources/js/components/form/EntryTypeSelect.vue b/resources/js/components/form/EntryTypeSelect.vue deleted file mode 100644 index 2498ff8fd51..00000000000 --- a/resources/js/components/form/EntryTypeSelect.vue +++ /dev/null @@ -1,295 +0,0 @@ - - - - - diff --git a/resources/js/composables/useAppendHtml.ts b/resources/js/composables/useAppendHtml.ts new file mode 100644 index 00000000000..af3799c484f --- /dev/null +++ b/resources/js/composables/useAppendHtml.ts @@ -0,0 +1,26 @@ +import {watch} from 'vue'; +import { + appendBodyHtml, + appendHeadHtml, +} from '../../../packages/craftcms-cp/src'; +import {usePage} from '@inertiajs/vue3'; + +export function useAppendHtml() { + const page = usePage<{ + headHtml?: string; + bodyHtml?: string; + }>(); + + watch( + () => [page.props.headHtml, page.props.bodyHtml], + async ([headHtml, bodyHtml]) => { + if (headHtml) { + await appendHeadHtml(headHtml); + } + + if (bodyHtml) { + await appendBodyHtml(bodyHtml); + } + } + ); +} diff --git a/resources/js/layout/AppLayout.vue b/resources/js/layout/AppLayout.vue index 5a0e7b2780f..f83d4927339 100644 --- a/resources/js/layout/AppLayout.vue +++ b/resources/js/layout/AppLayout.vue @@ -9,6 +9,7 @@ import Breadcrumbs from '@/components/Breadcrumbs.vue'; import {useAnnouncer} from '@/composables/useAnnouncer'; import LiveRegion from '@/components/LiveRegion.vue'; + import {useAppendHtml} from '@/composables/useAppendHtml'; withDefaults( defineProps<{ @@ -38,6 +39,9 @@ watch(successFlash, (newMessage) => announce(newMessage)); watch(errorFlash, (newMessage) => announce(newMessage)); + // Inject headHtml and bodyHtml when present + useAppendHtml(); + const state = reactive<{ sidebar: { mode: 'docked' | 'floating'; diff --git a/resources/js/pages/SettingsSectionsEditPage.vue b/resources/js/pages/SettingsSectionsEditPage.vue index 70e05db0f2b..0569be2a319 100644 --- a/resources/js/pages/SettingsSectionsEditPage.vue +++ b/resources/js/pages/SettingsSectionsEditPage.vue @@ -11,7 +11,7 @@ import CraftInputHandle from '@craftcms/cp/vue/CraftInputHandle.vue'; import CraftSwitch from '@craftcms/cp/vue/CraftSwitch.vue'; import CraftSelect from '@craftcms/cp/vue/CraftSelect.vue'; - import EntryTypeSelect from '@/components/form/EntryTypeSelect.vue'; + import EntryTypeSelect from '@/components/form/ComponentSelect/EntryTypeSelect.vue'; import {useInputGenerator} from '@/composables/useInputGenerator'; import type { SectionResource, @@ -298,8 +298,15 @@ }}

diff --git a/resources/templates/_includes/disclosuremenu.twig b/resources/templates/_includes/disclosuremenu.twig index 63c9d2fb744..3a4008921ab 100644 --- a/resources/templates/_includes/disclosuremenu.twig +++ b/resources/templates/_includes/disclosuremenu.twig @@ -33,8 +33,9 @@ }|filter|keys, }|merge(item.liAttributes ?? {}) %} {% set selected = item.selected ?? false %} - {% tag (type == 'button' ? 'button' : 'a') with { + {% tag 'craft-action-item' with { id: id, + icon: item.icon ?? null, class: { 'menu-item': true, sel: selected, @@ -54,15 +55,6 @@ }|filter, }|merge(item.attributes ?? {}, recursive=true) %} {%- apply spaceless %} - {% if item.icon ?? false %} - {{ tag('span', { - class: [ - 'icon', - _self.color(item.color ?? null), - ]|filter, - html: iconSvg(item.icon), - }) }} - {% endif %} {% if item.status ?? false -%} {{ statusIndicator(item.status) }} {%- endif -%} @@ -85,7 +77,7 @@ {% endtag %} {% if type == 'link' %} {% js %} - $('#{{ id|namespaceInputId }}').on('keydown', (ev) => { + $(document.body).on('keydown', '#{{ id|namespaceInputId }}' (ev) => { if (ev.keyCode === Garnish.SPACE_KEY) { ev.currentTarget.click(); } @@ -93,14 +85,16 @@ {% endjs %} {% endif %} {% js %} - $('#{{ id|namespaceInputId }}').on('activate', () => { + $(document.body).on('activate', '#{{ id|namespaceInputId }}', () => { setTimeout(() => { + console.log('{{menuId|namespaceInputId}}'); $('#{{ menuId|namespaceInputId }}').data('disclosureMenu').hide(); }, 1); }); {% endjs %} {% endmacro %} + {% if withButton %} {% if html ?? false %} {{ html|raw }} @@ -108,16 +102,15 @@ {{ label }} {% endif %} - {% tag 'button' with { - class: ['btn', 'menubtn'], + {% tag 'craft-button' with { type: 'button', + size: 'small', + icon: true, + appearance: 'plain', + slot: 'invoker', aria: { - controls: id, label: hiddenLabel ?? null }, - data: { - 'disclosure-trigger': true, - }, disabled: disabled ?? false, }|merge(buttonAttributes ?? {}, recursive=true) %} {%- apply spaceless %} @@ -140,7 +133,8 @@ {% tag 'div' with { id: id, - class: (class ?? [])|explodeClass|merge(['menu', 'menu--disclosure']), + class: (class ?? [])|explodeClass, + slot: 'content', data: { 'with-search-input': withSearchInput, }, @@ -203,3 +197,4 @@ {% if ulStarted %}{{ ''|raw }}{% endif %} {% endblock %} {% endtag %} + diff --git a/resources/views/components/icon.blade.php b/resources/views/components/icon.blade.php new file mode 100644 index 00000000000..02bce69bb3b --- /dev/null +++ b/resources/views/components/icon.blade.php @@ -0,0 +1,3 @@ +merge([ + 'name' => $name +])}}> diff --git a/resources/views/components/tooltip.blade.php b/resources/views/components/tooltip.blade.php new file mode 100644 index 00000000000..e76632e590b --- /dev/null +++ b/resources/views/components/tooltip.blade.php @@ -0,0 +1,5 @@ + + {!! $getContent() !!} + + +{!! $getButton() !!} diff --git a/routes/cp.php b/routes/cp.php index 8bd2e69fa5b..8a8cc759168 100644 --- a/routes/cp.php +++ b/routes/cp.php @@ -33,6 +33,7 @@ use CraftCms\Cms\Http\Controllers\Settings\UserGroupsController; use CraftCms\Cms\Http\Controllers\Settings\UserSettingsController; use CraftCms\Cms\Http\Controllers\Settings\VolumesController; +use CraftCms\Cms\Http\Controllers\UiController; use CraftCms\Cms\Http\Controllers\Updates\UpdaterController; use CraftCms\Cms\Http\Controllers\Users\AddressesController; use CraftCms\Cms\Http\Controllers\Users\PasskeysController; @@ -254,4 +255,6 @@ }); Route::post('updates', [UpdaterController::class, 'index']); + + Route::get('ui/{type}/{id}/{component}', [UiController::class, 'render']); }); diff --git a/src/Cp/Html/ElementHtml.php b/src/Cp/Html/ElementHtml.php index cf06b529616..f5a4a19ef11 100644 --- a/src/Cp/Html/ElementHtml.php +++ b/src/Cp/Html/ElementHtml.php @@ -28,6 +28,7 @@ use CraftCms\Cms\Support\Facades\HtmlStack; use CraftCms\Cms\Support\Facades\InputNamespace; use CraftCms\Cms\Support\Html; +use CraftCms\Cms\View\Components\Tooltip; use Illuminate\Container\Attributes\Singleton; use Illuminate\Support\Facades\Auth; use yii\base\InvalidConfigException; @@ -39,6 +40,8 @@ { public const string CHIP_SIZE_SMALL = 'small'; + public const string CHIP_SIZE_MEDIUM = 'medium'; + public const string CHIP_SIZE_LARGE = 'large'; public function __construct( @@ -82,17 +85,12 @@ public function chipHtml(Chippable $component, array $config = []): string $attributes = Arr::merge([ 'id' => $config['id'], + 'size' => $config['size'], 'class' => [ - 'chip', - $config['size'], + 'cp-colorable', + 'cp-colorable--'.$color?->value ?? 'white', ...Html::explodeClass($config['class']), ], - 'style' => array_filter([ - '--custom-border-color' => $color?->cssVar(200), - '--custom-bg-color' => $color?->cssVar(50), - '--custom-text-color' => $color?->cssVar(900), - '--custom-sel-bg-color' => $color?->cssVar(900), - ]), 'data' => array_filter([ 'type' => $component::class, 'id' => $component->getId(), @@ -112,34 +110,40 @@ public function chipHtml(Chippable $component, array $config = []): string ]), ], $config['attributes']); - $html = Html::beginTag('div', $attributes); + $html = Html::beginTag('craft-chip', $attributes); + + $prefixHtml = ''; + if ($config['showThumb'] || $config['selectable'] || $config['showStatus']) { + $prefixHtml = Html::beginTag('div', ['slot' => 'prefix']); + } if ($config['showThumb']) { if ($component instanceof Thumbable) { $thumbSize = $config['size'] === self::CHIP_SIZE_SMALL ? 30 : 120; - $html .= $component->getThumbHtml($thumbSize) ?? ''; + $prefixHtml .= $component->getThumbHtml($thumbSize) ?? ''; } else { /** @var Chippable&Iconic $component */ $icon = $component->getIcon(); if ($icon || $icon === '0') { - $html .= Html::tag('div', Icons::svg($icon), [ - 'class' => array_filter(['thumb', 'cp-icon', $color?->value]), + $prefixHtml .= Html::tag('craft-icon', '', [ + 'name' => $icon, ]); } } } - $html .= Html::beginTag('div', ['class' => 'chip-content']); - if ($config['selectable']) { - $html .= $this->componentCheckboxHtml(sprintf('%s-label', $config['id'])); + $prefixHtml .= $this->componentCheckboxHtml(sprintf('%s-label', $config['id'])); } if ($config['showStatus']) { /** @var Chippable&Statusable $component */ - $html .= $this->statusHtml->componentStatusIndicatorHtml($component) ?? ''; + $prefixHtml .= $this->statusHtml->componentStatusIndicatorHtml($component) ?? ''; } + $prefixHtml .= Html::endTag('div'); + $html .= $prefixHtml; + if (isset($config['labelHtml'])) { $html .= $config['labelHtml']; } elseif ($config['showLabel']) { @@ -155,20 +159,21 @@ public function chipHtml(Chippable $component, array $config = []): string /** @var Chippable&Describable $component */ $description = $component->getDescription(); if ($description) { - $labelHtml .= Html::tag('span', - $this->contentHtml->parseMarkdown(Html::encode($description)), - ['class' => 'info']); + $labelHtml .= Tooltip::make() + ->id('description-tooltip-'.$config['id']) + ->content($description) + ->toHtml(); } } - $labelHtml = Html::tag('div', $labelHtml); + $labelHtml = Html::tag('div', $labelHtml, ['class' => 'flex gap-1 items-center']); if ($config['showHandle']) { /** @var Chippable&Grippable $component */ $handle = $component->getHandle(); if ($handle) { $labelHtml .= Html::tag('div', Html::encode($handle), [ - 'class' => ['smalltext', 'light', 'code'], + 'class' => ['cp-code'], ]); } } @@ -178,15 +183,12 @@ public function chipHtml(Chippable $component, array $config = []): string if (! empty($indicators)) { $labelHtml .= Html::beginTag('div', ['class' => 'indicators']). implode('', array_map(function (array $indicator) { - $color = $indicator['iconColor'] ?? null; - if ($color instanceof Color) { - $color = $color->value; - } - - return Html::tag('div', Icons::svg($indicator['icon']), [ - 'class' => array_filter(['cp-icon', 'puny', $color]), - 'title' => $indicator['label'], - 'aria' => ['label' => $indicator['label']], + $color = Color::tryFrom($indicator['iconColor']); + + return Html::tag('craft-icon', '', [ + 'name' => $indicator['icon'], + 'style' => $color ? ['color' => $color->cssVar(600)] : null, + 'label' => $indicator['label'], ]); }, $indicators)). Html::endTag('div'); @@ -194,11 +196,11 @@ public function chipHtml(Chippable $component, array $config = []): string } $html .= Html::tag('div', $labelHtml, [ 'id' => sprintf('%s-label', $config['id']), - 'class' => 'chip-label', + 'class' => 'grid gap-1 justify-items-start', ]); } - $html .= Html::beginTag('div', ['class' => 'chip-actions']); + $html .= Html::beginTag('div', ['slot' => 'suffix']); if ($config['showActionMenu']) { /** @var Chippable&Actionable $component */ $html .= $this->componentActionMenu($component); @@ -217,14 +219,16 @@ public function chipHtml(Chippable $component, array $config = []): string ], ]); } - $html .= Html::endTag('div'); // .chip-actions + $html .= Html::endTag('div'); // slot=suffix if ($config['inputName'] !== null) { $inputValue = $config['inputValue'] ?? $component->getId(); $html .= Html::hiddenInput($config['inputName'], (string) $inputValue); } // .element - return $html.(Html::endTag('div').Html::endTag('div')); + $html .= Html::endTag('craft-chip'); + + return $html; } public function elementChipHtml(ElementInterface $element, array $config = []): string @@ -676,15 +680,12 @@ function () use ($component, $withEdit): string { } return $this->menuHtml->disclosureMenu($actionMenuItems, [ - 'hiddenLabel' => t('Actions'), + 'buttonHtml' => Html::tag('craft-icon', '', ['name' => 'ellipsis', 'label' => t('Actions')]), 'buttonAttributes' => [ - 'class' => array_keys(array_filter([ - 'action-btn' => true, - 'small' => true, - 'hidden' => empty($actionMenuItems), - ])), - 'removeClass' => 'menubtn', - 'data' => ['icon' => 'ellipsis'], + 'icon' => true, + 'size' => 'small', + 'appearance' => 'plain', + 'variant' => 'inherit', ], 'omitIfEmpty' => false, ]); diff --git a/src/Entry/Data/EntryType.php b/src/Entry/Data/EntryType.php index 5abef586586..ef1578377bb 100644 --- a/src/Entry/Data/EntryType.php +++ b/src/Entry/Data/EntryType.php @@ -182,7 +182,7 @@ public function getActionMenuItems(): array ]]; HtmlStack::jsWithVars(fn ($id, $params) => << { +$(document.body).on('click', '#' + $id, () => { new Craft.CpScreenSlideout('entry-types/edit', { params: $params, }) diff --git a/src/Entry/Resources/EntryTypeResource.php b/src/Entry/Resources/EntryTypeResource.php index e68b7fd93b3..b34ebcad7f4 100644 --- a/src/Entry/Resources/EntryTypeResource.php +++ b/src/Entry/Resources/EntryTypeResource.php @@ -18,13 +18,7 @@ public function toArray(Request $request): array $elementHtml = app(ElementHtml::class); return parent::toArray($request) + [ - 'chipHtml' => $elementHtml->chipHtml($this->resource, [ - 'showHandle' => true, - // 'checkbox' => $this->resource->selectable, - 'showActionMenu' => true, - 'showIndicators' => true, - 'showDescription' => true, - ]), + 'chipHtml' => $elementHtml->chipHtml($this->resource, $request->chipConfig), ]; } } diff --git a/src/Http/Controllers/Settings/SectionsController.php b/src/Http/Controllers/Settings/SectionsController.php index cdcbf9c5fd4..4cb213e02a8 100644 --- a/src/Http/Controllers/Settings/SectionsController.php +++ b/src/Http/Controllers/Settings/SectionsController.php @@ -11,6 +11,7 @@ use CraftCms\Cms\Element\Element; use CraftCms\Cms\Element\Enums\PropagationMethod; use CraftCms\Cms\Entry\EntryTypes; +use CraftCms\Cms\Entry\Resources\EntryTypeResource; use CraftCms\Cms\Http\RespondsWithFlash; use CraftCms\Cms\Http\Responses\CpScreenResponse; use CraftCms\Cms\Section\Data\Section as SectionData; @@ -101,11 +102,21 @@ public function create(Sites $sites): CpScreenResponse ->inertiaPage('SettingsSectionsEditPage', $this->sectionProps($section, $sites, brandNew: true)); } - public function edit(Sections $sections, Sites $sites, SectionModel $section): CpScreenResponse + public function edit(Request $request, Sections $sections, Sites $sites, SectionModel $section): CpScreenResponse { $sectionData = $sections->getSectionById($section->id); abort_if(is_null($sectionData), 404, 'Section not found'); + // Configure our entry type chips + $request->merge([ + 'chipConfig' => [ + 'showHandle' => true, + 'showActionMenu' => true, + 'showIndicators' => true, + 'showDescription' => true, + ], + ]); + return new CpScreenResponse() ->title(trim($sectionData->name) ?: t('Edit Section')) ->addCrumb(t('Settings'), 'settings') @@ -137,7 +148,7 @@ private function sectionProps(SectionData $section, Sites $sites, bool $brandNew 'section' => SectionResource::make($section), 'homepageUri' => Element::HOMEPAGE_URI, 'brandNew' => $brandNew, - 'entryTypes' => $this->entryTypes->getAllEntryTypes(), + 'entryTypes' => EntryTypeResource::collection($this->entryTypes->getAllEntryTypes()), 'typeOptions' => SectionType::asOptions(), 'propagationOptions' => PropagationMethod::asOptions(), 'placementOptions' => DefaultPlacement::asOptions(), diff --git a/src/Shared/Enums/Color.php b/src/Shared/Enums/Color.php index b5cb9ae0f25..1ee3520f111 100644 --- a/src/Shared/Enums/Color.php +++ b/src/Shared/Enums/Color.php @@ -55,8 +55,8 @@ public function cssVar(int $shade): string } return match ($this) { - self::White, self::Gray, self::Black => sprintf('var(--%s)', $this->value), - default => sprintf('var(--%s-%s)', $this->value, str_pad((string) $shade, 3, '0', STR_PAD_LEFT)), + self::White, self::Gray, self::Black => sprintf('var(--color-%s)', $this->value), + default => sprintf('var(--color-%s-%s)', $this->value, str_pad((string) $shade, 3, '0', STR_PAD_LEFT)), }; } } diff --git a/src/View/Components/Concerns/HasId.php b/src/View/Components/Concerns/HasId.php new file mode 100644 index 00000000000..fa7d71976d2 --- /dev/null +++ b/src/View/Components/Concerns/HasId.php @@ -0,0 +1,22 @@ +id = $id; + + return $this; + } + + public function getId(): string + { + return $this->id; + } +} diff --git a/src/View/Components/Tooltip.php b/src/View/Components/Tooltip.php new file mode 100644 index 00000000000..c2c1a9aab2c --- /dev/null +++ b/src/View/Components/Tooltip.php @@ -0,0 +1,84 @@ +placement = $value; + + return $this; + } + + public function button(?string $value = null): static + { + $this->button = $value; + + return $this; + } + + public function content(?string $value = null): static + { + $this->content = $value; + + return $this; + } + + public function getContent(): string + { + return app(ContentHtml::class)->parseMarkdown(Html::encode($this->content)); + } + + private function getDefaultIcon(): string + { + return Html::tag('craft-icon', '', [ + 'name' => 'circle-info', + ]); + } + + public function getButton(): string + { + if ($this->button) { + return $this->button; + } + + return Html::tag('craft-button', $this->getDefaultIcon(), [ + 'id' => $this->getId(), + 'appearance' => 'plain', + 'variant' => 'inherit', + 'icon' => true, + 'size' => 'zero', + ]); + } + + public static function make(array $config = []): static + { + return app(static::class, $config); + } +} diff --git a/src/View/Components/ViewComponent.php b/src/View/Components/ViewComponent.php new file mode 100644 index 00000000000..b086e07945d --- /dev/null +++ b/src/View/Components/ViewComponent.php @@ -0,0 +1,131 @@ +viewData[] = $data; + + return $this; + } + + /** + * @template T + * + * @param T | callable(): T $value + * @return T + */ + public function evaluate(mixed $value): mixed + { + if (! $value instanceof Closure) { + return $value; + } + + return $value(); + } + + protected function extractPublicMethods(): array + { + $reflection = new ReflectionClass($this); + + $methods = []; + + foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + $methods[$method->getName()] = Closure::fromCallable([$this, $method->getName()]); + } + + return $methods; + } + + protected function extractPublicProperties(): array + { + $reflection = new ReflectionClass($this); + + $properties = []; + + foreach ($reflection->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { + if (! $property->isStatic()) { + $properties[$property->getName()] = $property->getValue($this); + } + } + + return $properties; + } + + public function getViewData(): array + { + return Arr::mapWithKeys( + $this->viewData, + fn (mixed $data): array => $this->evaluate($data) ?? [], + ); + } + + /** + * Set the view to be rendered. + */ + public function view(?string $view, array|Closure $viewData = []): static + { + if ($view === null) { + return $this; + } + + $this->view = $view; + + if (filled($viewData)) { + $this->viewData($viewData); + } + + return $this; + } + + /** + * @return array + */ + public function getExtraViewData(): array + { + return []; + } + + public function getView(): string + { + if (isset($this->view)) { + return $this->view; + } + + throw new Exception('Class ['.static::class.'] extends ['.ViewComponent::class.'] but does not have a [$view] property defined.'); + } + + public function render(): View + { + return view($this->getView(), [ + ...$this->extractPublicMethods(), + ...$this->extractPublicProperties(), + 'attributes' => new ComponentAttributeBag, + ...$this->getExtraViewData(), + ...$this->getViewData(), + ]); + } + + public function toHtml(): string + { + return $this->render()->render(); + } +} diff --git a/yii2-adapter/.stylelintrc.json b/yii2-adapter/.stylelintrc.json index b3c44260f12..aeb14ad2519 100644 --- a/yii2-adapter/.stylelintrc.json +++ b/yii2-adapter/.stylelintrc.json @@ -8,12 +8,7 @@ "declaration-empty-line-before": null, "no-descending-specificity": null, "no-duplicate-selectors": null, - "no-invalid-position-at-import-rule": [ - true, - { - "ignoreAtRules": ["tailwind", "use"] - } - ], + "no-invalid-position-at-import-rule": null, "selector-class-pattern": null, "liberty/use-logical-spec": [ "always", diff --git a/yii2-adapter/legacy/web/assets/cp/src/css/_cp.scss b/yii2-adapter/legacy/web/assets/cp/src/css/_cp.scss index 6041a22c3d0..6141833e550 100644 --- a/yii2-adapter/legacy/web/assets/cp/src/css/_cp.scss +++ b/yii2-adapter/legacy/web/assets/cp/src/css/_cp.scss @@ -1577,21 +1577,6 @@ li.breadcrumb-toggle-wrapper { } } -/* grids */ -.grid { - position: relative; - min-height: 1px; // Required for Grid.js to run - - &::after { - @include mixins.clearafter; - } - - & > .item { - display: none; - box-sizing: border-box; - } -} - %type-heading-small { text-transform: uppercase; color: var(--medium-text-color); diff --git a/yii2-adapter/legacy/web/assets/cp/src/css/craft.scss b/yii2-adapter/legacy/web/assets/cp/src/css/craft.scss index 43174a9bbfe..291dcd27706 100644 --- a/yii2-adapter/legacy/web/assets/cp/src/css/craft.scss +++ b/yii2-adapter/legacy/web/assets/cp/src/css/craft.scss @@ -4,18 +4,21 @@ // @use 'tokens'; // @use 'variables'; @use 'compat'; -@use 'main'; -@use 'cp'; -@use 'range'; -@use 'global-sidebar'; -@use 'craft-disclosure'; -@use 'craft-spinner'; -@use 'craft-tooltip'; -@use 'preview'; -@use 'login'; -@use 'entry-type-select'; -@use 'fld'; -@use 'grouped-entry-type-select'; -@use 'image_editor'; -@use 'shame'; -@use 'debug_toolbar'; + +@scope (.cp-legacy-reset, .slideout-container, .menu--disclosure) { + @import 'main'; + @import 'cp'; + @import 'range'; + @import 'global-sidebar'; + @import 'craft-disclosure'; + @import 'craft-spinner'; + @import 'craft-tooltip'; + @import 'preview'; + @import 'login'; + @import 'entry-type-select'; + @import 'fld'; + @import 'grouped-entry-type-select'; + @import 'image_editor'; + @import 'shame'; + @import 'debug_toolbar'; +}