diff --git a/packages/craftcms-legacy/cp/src/css/_main.scss b/packages/craftcms-legacy/cp/src/css/_main.scss index c318ee3830a..03a7101f51e 100644 --- a/packages/craftcms-legacy/cp/src/css/_main.scss +++ b/packages/craftcms-legacy/cp/src/css/_main.scss @@ -1154,33 +1154,15 @@ ul.icons { gap: var(--s); } -.gap-0 { - gap: 0; -} - -.gap-2xs { - gap: var(--2xs); -} - -.gap-xs { - gap: var(--xs); -} - -.gap-s { - gap: var(--s); -} - -.gap-m { - gap: var(--m); -} - -.gap-l { - gap: var(--l); -} - -.gap-xl { - gap: var(--xl); -} +/* +The gap utilities that lived here are gone; use the `cp:gap-*` utilities from +`resources/css/utilities.css`, which both CP shells now load. Note the scales +are named differently — legacy `--xs` is `--c-spacing-sm`, not `-xs` — so the +mapping shifts by one step: + + gap-2xs -> cp:gap-xs gap-s -> cp:gap-md gap-l -> cp:gap-lg + gap-xs -> cp:gap-sm gap-m -> cp:gap-lg gap-xl -> cp:gap-xl +*/ .spacer { width: 14px; diff --git a/packages/craftcms-legacy/cp/src/js/UI.js b/packages/craftcms-legacy/cp/src/js/UI.js index 56d9c61290c..cd408d14a80 100644 --- a/packages/craftcms-legacy/cp/src/js/UI.js +++ b/packages/craftcms-legacy/cp/src/js/UI.js @@ -32,7 +32,7 @@ Craft.ui = { } let $iconContainer; if (config.icon || config.label || config.html) { - const $labelContainer = $('
').appendTo( + const $labelContainer = $('').appendTo( $btn ); if (config.icon) { diff --git a/packages/craftcms-ui/package.json b/packages/craftcms-ui/package.json index e20b513aa60..6c6d412400e 100644 --- a/packages/craftcms-ui/package.json +++ b/packages/craftcms-ui/package.json @@ -31,7 +31,7 @@ "build:storybook": "storybook build", "build:manifest": "custom-elements-manifest analyze --litelement --outdir dist", "generate:vue-wrappers": "node ./scripts/generate-vue-wrappers.js", - "generate:colors": "node ./scripts/generate-colors.js && node scripts/generate-color-palette.js" + "generate:colors": "node ./scripts/generate-colors.js && node scripts/generate-color-palette.js && node ./scripts/generate-tailwind.js" }, "exports": { "./package.json": "./package.json", diff --git a/packages/craftcms-ui/scripts/build.js b/packages/craftcms-ui/scripts/build.js index d61b6315868..810172d4c5a 100755 --- a/packages/craftcms-ui/scripts/build.js +++ b/packages/craftcms-ui/scripts/build.js @@ -10,6 +10,7 @@ import {dirname, join, relative, resolve} from 'path'; import {deleteAsync} from 'del'; import createVueWrappers from './generate-vue-wrappers.js'; import createColors from './generate-colors.js'; +import createTailwindTheme from './generate-tailwind.js'; const spinner = ora({text: '@craftcms/ui', color: 'red'}).start(); const isDeveloping = process.argv.includes('--develop'); @@ -250,6 +251,13 @@ async function generateColors() { return Promise.resolve(); } +async function generateTailwindTheme() { + spinner.start('Generating Tailwind Theme'); + await createTailwindTheme(); + spinner.succeed(); + return Promise.resolve(); +} + async function buildAll() { try { const steps = [ @@ -258,6 +266,7 @@ async function buildAll() { generateStyles, generateVueWrappers, generateColors, + generateTailwindTheme, generateBundle, pruneOrphanedChunks, assertDeclarationsResolved, diff --git a/packages/craftcms-ui/scripts/generate-colors.js b/packages/craftcms-ui/scripts/generate-colors.js index 7627b126b8e..bdd661c7180 100644 --- a/packages/craftcms-ui/scripts/generate-colors.js +++ b/packages/craftcms-ui/scripts/generate-colors.js @@ -1,28 +1,12 @@ -import {writeFileSync, readFileSync} from 'fs'; +import {writeFileSync} from 'fs'; import {dirname, resolve} from 'path'; import {fileURLToPath} from 'url'; -import {transformSync} from 'esbuild'; +import {loadColorData} from './utils.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = resolve(__dirname, '..'); const OUT_FILE = resolve(ROOT, 'src/styles/shared/colorable.css'); const OUT_LIT_FILE = resolve(ROOT, 'src/styles/colorable.styles.ts'); -const DATA_FILE = resolve(ROOT, 'src/constants/colors.data.ts'); - -/* -The canonical color data lives in src/constants/colors.data.ts (shared with -constants/colors.ts). This script can't import TypeScript directly, so we -transpile it with esbuild and import the result. - */ -async function loadColorData() { - const {code} = transformSync(readFileSync(DATA_FILE, 'utf8'), { - loader: 'ts', - format: 'esm', - }); - return import( - `data:text/javascript;base64,${Buffer.from(code).toString('base64')}` - ); -} const colorIncrements = { fill: { @@ -127,18 +111,51 @@ function buildSemanticTokens(semanticColors) { return declarations.join('\n\n'); } +/** + * The generic tokens, remapped to one color group. + * + * Anything carrying a palette selector paints in that group, and so does + * everything under it: components author against the generic tokens, so they + * inherit the group without knowing which one is active. + */ +function buildDeclarations(color, indent = ' ') { + return ['fill', 'border', 'on'] + .flatMap((role) => + ['quiet', 'normal', 'loud'].map( + (strength) => + `${indent}--c-color-${role}-${strength}: var(--c-color-${color}-${role}-${strength});` + ) + ) + .join('\n'); +} + +/** + * The selectors that put an element in a color group. + * + * `.cp-palette-*` and `[data-palette]` are the current spellings — the class + * follows the CP's `.cp-*` class convention, and the attribute matches the + * class, so the two read as one idea rather than two. It is a plain stylesheet + * rule, not a Tailwind utility, so it carries no `cp:` prefix and works in the + * legacy shell and inside shadow roots as well. + * + * `.cp-color-*` and `[data-color]` are the Craft 5 spellings. They stay on the + * same rule so existing markup and plugins keep working; new code should reach + * for the palette spellings. Note that `[data-color]` is *not* the same axis as + * `[data-theme='dark']`, which selects the light/dark color scheme — which is + * why this one couldn't simply become `data-theme`. + */ +function paletteSelectors(color) { + return [ + `.cp-palette-${color}`, + `[data-palette='${color}']`, + `.cp-color-${color}`, + `[data-color='${color}']`, + ]; +} + function buildStyleBlock(color) { - return `.cp-color-${color}, -[data-color='${color}'] { - --c-color-fill-quiet: var(--c-color-${color}-fill-quiet); - --c-color-border-quiet: var(--c-color-${color}-border-quiet); - --c-color-on-quiet: var(--c-color-${color}-on-quiet); - --c-color-fill-normal: var(--c-color-${color}-fill-normal); - --c-color-border-normal: var(--c-color-${color}-border-normal); - --c-color-on-normal: var(--c-color-${color}-on-normal); - --c-color-fill-loud: var(--c-color-${color}-fill-loud); - --c-color-border-loud: var(--c-color-${color}-border-loud); - --c-color-on-loud: var(--c-color-${color}-on-loud); + return `${paletteSelectors(color).join(',\n')} { +${buildDeclarations(color)} }`; } @@ -164,15 +181,7 @@ ${[...paletteColors, ...Object.keys(semanticColors)] */ function buildLitBlock(selectors, color) { return ` ${selectors.join(',\n ')} { - --c-color-fill-quiet: var(--c-color-${color}-fill-quiet); - --c-color-border-quiet: var(--c-color-${color}-border-quiet); - --c-color-on-quiet: var(--c-color-${color}-on-quiet); - --c-color-fill-normal: var(--c-color-${color}-fill-normal); - --c-color-border-normal: var(--c-color-${color}-border-normal); - --c-color-on-normal: var(--c-color-${color}-on-normal); - --c-color-fill-loud: var(--c-color-${color}-fill-loud); - --c-color-border-loud: var(--c-color-${color}-border-loud); - --c-color-on-loud: var(--c-color-${color}-on-loud); +${buildDeclarations(color, ' ')} }`; } @@ -180,7 +189,12 @@ function generateLitStyles(paletteColors, semanticColors) { const palette = [...paletteColors, ...Object.keys(semanticColors)] .map((color) => buildLitBlock( - [`:host([data-color='${color}'])`, `[data-color='${color}']`], + [ + `:host([data-palette='${color}'])`, + `[data-palette='${color}']`, + `:host([data-color='${color}'])`, + `[data-color='${color}']`, + ], color ) ) @@ -204,9 +218,9 @@ import {css} from 'lit'; * chip, a badge, an icon). Most components want {@link variantStyles} instead * — this is every color in the system, so adopting it isn't free. * - * Matches a host carrying \`data-color\`, or any element inside the shadow - * root that carries it, mirroring the \`[data-color]\` rules in - * \`shared/colorable.css\`. + * Matches a host carrying \`data-palette\` (or its deprecated \`data-color\` + * spelling), or any element inside the shadow root that carries it, mirroring + * the palette rules in \`shared/colorable.css\`. */ export const paletteStyles = css\` ${palette} diff --git a/packages/craftcms-ui/scripts/generate-tailwind.js b/packages/craftcms-ui/scripts/generate-tailwind.js new file mode 100644 index 00000000000..99c515305da --- /dev/null +++ b/packages/craftcms-ui/scripts/generate-tailwind.js @@ -0,0 +1,174 @@ +import {writeFileSync} from 'fs'; +import {dirname, resolve} from 'path'; +import {fileURLToPath} from 'url'; +import {loadColorData} from './utils.js'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT = resolve(__dirname, '..'); +const OUT_FILE = resolve(ROOT, 'tailwind.css'); + +/** + * Utility names are unprefixed. CP code only ever runs in the CP, where this + * is the only Tailwind there is, so there is nothing to disambiguate a Craft + * utility from at the point of use — `bg-loud` reads no differently than + * `flex`, and both are equally the CP's. + */ + +/** The three intensities every colorable token group exposes. */ +const strengths = ['quiet', 'normal', 'loud']; + +/** + * The named steps on Craft's spacing scale. + * + * The scale's `--c-spacing` base is deliberately left out: it's the unit the + * other steps multiply, not a step you'd reach for on an element. + */ +const spacingSteps = ['1px', 'xs', 'sm', 'md', 'lg', 'xl', '2xl']; + +/** + * Utility stem → property, matching Tailwind's own spacing utilities so + * `c-px-md` means what `px-4` means: `x`/`y` are logical (`padding-inline`), + * `s`/`e` are the logical edges, and `t`/`r`/`b`/`l` stay physical. + */ +const spacingProperties = [ + ['p', 'padding'], + ['px', 'padding-inline'], + ['py', 'padding-block'], + ['ps', 'padding-inline-start'], + ['pe', 'padding-inline-end'], + ['pt', 'padding-top'], + ['pr', 'padding-right'], + ['pb', 'padding-bottom'], + ['pl', 'padding-left'], + ['m', 'margin'], + ['mx', 'margin-inline'], + ['my', 'margin-block'], + ['ms', 'margin-inline-start'], + ['me', 'margin-inline-end'], + ['mt', 'margin-top'], + ['mr', 'margin-right'], + ['mb', 'margin-bottom'], + ['ml', 'margin-left'], + ['gap', 'gap'], + ['gap-x', 'column-gap'], + ['gap-y', 'row-gap'], +]; + +function utility(name, declarations) { + return [ + `@utility ${name} {`, + ...declarations.map(([property, value]) => ` ${property}: ${value};`), + '}', + ].join('\n'); +} + +function section(heading, rules) { + return [`/* ——— ${heading} ——— */`, '', rules.join('\n\n')].join('\n'); +} + +/** + * The four color utilities a token group exposes, at each strength. + * + * `group` is a semantic name (`danger`) or `null` for the generic tokens, + * which hold whatever the nearest `[data-color]` / `variant` resolved to. + * The role is implied by the property rather than spelled in the class: + * `fill` paints backgrounds, `border` paints borders, `on` paints the text + * that sits *on* a fill. Text gets both — `c-text-loud` uses the fill color + * as ink, `c-text-on-loud` uses the color meant to be legible against it. + */ +function colorUtilities(group) { + const token = (role, strength) => + `var(--c-color-${group ? `${group}-` : ''}${role}-${strength})`; + const name = (stem, strength) => + `${stem}-${group ? `${group}-` : ''}${strength}`; + + return strengths.flatMap((strength) => [ + utility(name('bg', strength), [ + ['background-color', token('fill', strength)], + ]), + utility(name('text', strength), [['color', token('fill', strength)]]), + utility(name('text-on', strength), [['color', token('on', strength)]]), + utility(name('border', strength), [ + ['border-color', token('border', strength)], + ]), + ]); +} + +function spacingUtilities() { + return spacingProperties.flatMap(([stem, property]) => + spacingSteps.map((step) => + utility(`${stem}-${step}`, [[property, `var(--c-spacing-${step})`]]) + ) + ); +} + +/** + * Craft's design tokens, republished as prefixed Tailwind utility classes. + * + * Colors cover the generic contextual tokens plus the semantic groups. The + * raw palette (`--c-color-red-*` and friends) stays reachable from CSS and + * from `[data-color]`, but isn't worth ~750 utility classes here. + */ +function generateUtilities(semanticColors) { + const sections = [ + section('Colors — current context', colorUtilities(null)), + ...Object.keys(semanticColors).map((group) => + section(`Colors — ${group}`, colorUtilities(group)) + ), + section('Spacing', spacingUtilities()), + ]; + + return `/* Auto-generated by scripts/generate-tailwind.js — do not edit manually */ + +/** + * Craft CMS Control Panel — Tailwind v4 utilities. + * + * Craft's design tokens as utility classes, unprefixed and sitting alongside + * stock Tailwind: \`bg-loud\` next to \`flex\`, \`p-md\` next to \`p-4\`. + * CP code only ever runs in the CP, so there is no raw-Tailwind context to + * tell these apart from. + * + * Colors read as \`Something went wrong
+ + +{{
diff --git a/resources/js/modules/elements/components/RevisionsList.vue b/resources/js/modules/elements/components/RevisionsList.vue
index 291f32b024a..fb4943b7949 100644
--- a/resources/js/modules/elements/components/RevisionsList.vue
+++ b/resources/js/modules/elements/components/RevisionsList.vue
@@ -89,7 +89,7 @@
-
+
{{ t('No drafts or revisions.') }}
diff --git a/resources/js/modules/elements/fixtures/elements.ts b/resources/js/modules/elements/fixtures/elements.ts
index 1fe3b72d7ae..eb036ca4407 100644
--- a/resources/js/modules/elements/fixtures/elements.ts
+++ b/resources/js/modules/elements/fixtures/elements.ts
@@ -148,7 +148,7 @@ export const sampleCards = sampleEntries.slice(0, 6).map((entry) => ({
class: ['card'],
data: {id: entry.id},
},
- cardHeaderHtml: `${entry.section}`,
+ cardHeaderHtml: `${entry.section}`,
cardContentHtml: ``,
cardFooterHtml: '',
}));
diff --git a/resources/js/modules/forms/HeadingNode.vue b/resources/js/modules/forms/HeadingNode.vue
index 2fc1473c00e..c1faf6894b9 100644
--- a/resources/js/modules/forms/HeadingNode.vue
+++ b/resources/js/modules/forms/HeadingNode.vue
@@ -22,10 +22,7 @@
{{ node.props.content }}
-
+
{{ node.props.description }}
diff --git a/resources/js/modules/sections/components/PreviewTargetsTable.vue b/resources/js/modules/sections/components/PreviewTargetsTable.vue
index 79924445847..e8e553cdee2 100644
--- a/resources/js/modules/sections/components/PreviewTargetsTable.vue
+++ b/resources/js/modules/sections/components/PreviewTargetsTable.vue
@@ -87,7 +87,7 @@
-
+