From 4e843bcf9a246757a5469eaa75b49b0fe652eae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dante=20=C3=81lvarez?= <89805481+danalvrz@users.noreply.github.com> Date: Tue, 7 Oct 2025 18:40:10 -0600 Subject: [PATCH 01/10] add updated sections --- .../advanced-components-bm3.md | 726 ++++++++++++++++++ .../block-development-widgets.md | 647 ++++++++++++++++ .../customizing-volto-light-theme/concepts.md | 249 +++++- .../creating-new-project.md | 11 - .../design-system-implementation.md | 453 +++++++++++ docs/customizing-volto-light-theme/index.md | 7 +- .../installing-vlt.md | 71 -- .../integrate-new-block.md | 44 -- docs/customizing-volto-light-theme/theming.md | 342 --------- 9 files changed, 2042 insertions(+), 508 deletions(-) create mode 100644 docs/customizing-volto-light-theme/advanced-components-bm3.md create mode 100644 docs/customizing-volto-light-theme/block-development-widgets.md delete mode 100644 docs/customizing-volto-light-theme/creating-new-project.md create mode 100644 docs/customizing-volto-light-theme/design-system-implementation.md delete mode 100644 docs/customizing-volto-light-theme/installing-vlt.md delete mode 100644 docs/customizing-volto-light-theme/integrate-new-block.md delete mode 100644 docs/customizing-volto-light-theme/theming.md diff --git a/docs/customizing-volto-light-theme/advanced-components-bm3.md b/docs/customizing-volto-light-theme/advanced-components-bm3.md new file mode 100644 index 000000000..b90172a9d --- /dev/null +++ b/docs/customizing-volto-light-theme/advanced-components-bm3.md @@ -0,0 +1,726 @@ +--- +myst: + html_meta: + "description": "Advanced Components, Site Customization & Block Model v3" + "property=og:description": "Advanced Components, Site Customization & Block Model v3" + "property=og:title": "Advanced Components, Site Customization & Block Model v3" + "keywords": "Plone, Volto, Training, Volto Light Theme, Integrate, block" +--- + +# 4. Advanced Components, Site Customization & Block Model v3 + +## 4.1 Understanding the Card Primitive + +The Card primitive is VLT's reusable component for displaying content in card layouts. It has three configurable slots: image, summary, and actions. + +### Card Structure + +```jsx + + + +

Title

+

Summary text goes here.

+
+ + + +
+``` + +### Card Variations + +- **Vertical layout** (default): Image on top +- **Horizontal layout**: Image on left or right +- **Contained**: With background color from theme +- **Listing**: Image displayed on left with specific size (controlled by `--card-listing-image-size`, default 220px) + +### Card.Image Slot + +Display an image using a `src` prop or use a Plone image object: + +```tsx + +``` + +Custom image component: + +```tsx + +``` + +### Card.Summary Slot + +Recommended structure using VLT's Summary component: + +```tsx +import DefaultSummary from '@kitconcept/volto-light-theme/components/Summary/DefaultSummary'; + +const Summary = config.getComponent({ + name: 'Summary', + dependencies: [item['@type']], +}).component || DefaultSummary; + + + + +``` + +## 4.2 Creating Custom Summary Components + +The Summary component displays content metadata in listings, teasers, and cards. VLT includes built-in implementations: + +- `DefaultSummary`: Kicker, title, and description +- `NewsItemSummary`: Publication date, kicker, title, description +- `EventSummary`: Start/end date, kicker, title, description +- `FileSummary`: File size and type + +### Step 1: Create Product Content Type + +First, create a custom "Product" content type through the Plone UI: + +1. Go to http://localhost:3000/controlpanel/dexterity-types +2. Click "Add New Content Type" +3. Fill in: + - **Type Name**: Product + - **Short Name**: product (auto-filled) + - **Description**: A product with pricing information +4. Click "Add" +5. In the "Behaviors" tab, enable: + - "Kicker" (we'll use this field to store the price) + - "Preview image" + - Any other desired behaviors (Dublin Core, etc.) +6. Click "Save" + +### Step 2: Create a Custom Summary Component + +Create `src/components/Summary/ProductSummary.jsx`: + +```jsx +import { FormattedNumber } from 'react-intl'; + +const ProductSummary = ({ item, HeadingTag = 'h3' }) => { + const { title, description, head_title, currency = 'EUR' } = item; + const price = parseFloat(head_title); + + return ( + <> + + {title ? title : item.id} + + {description &&

{description}

} + {head_title && !isNaN(price) && ( +
+ + + +
+ )} + + ); +}; + +ProductSummary.hideLink = false; +export default ProductSummary; +``` + +**Note**: We use the `head_title` field (kicker) to store price information. The price is formatted using `FormattedNumber` for proper currency display with EUR as default. In a real project, you would create custom fields for price and currency. + +### Step 3: Add Styles for Product Summary + +Create `src/theme/_productSummary.scss`: + +```scss +.products .card .card-summary { + padding-bottom: 0 !important; + .product-price { + font-size: 1.25rem; + font-weight: var(--font-bold); + color: var(--accent-color); + margin-bottom: 0.5rem; + margin-top: 0.5rem; + + .price { + background: var(--theme-high-contrast-color); + padding: 0.25rem 0.75rem; + border-radius: 4px; + } + } + + .product-title { + margin-top: 0; + } +} +``` + +Import in `src/theme/_main.scss`: + +```scss +@import './blocks/hero'; +@import './blocks/relatedItems'; +@import './productSummary'; +@import './site'; +``` + +### Step 4: Register the Summary Component + +In `src/config/settings.ts`: + +```typescript +import ProductSummary from '../components/Summary/ProductSummary'; + +export default function install(config: ConfigType) { + // ... previous config ... + + config.registerComponent({ + name: 'Summary', + component: ProductSummary, + dependencies: ['product'], + }); + + return config; +} +``` + +### Step 5: Test the Product Summary + +1. Create a Product item in your site +2. Fill in the title, description, and kicker field (e.g., "$99.99") +3. Add the Product to a Listing or Teaser block +4. The custom ProductSummary will display the price, title, and description + +## 4.3 Creating Custom Listing Variations with Card Actions + +Listing variations customize content display in Listing blocks. Let's create a ProductTemplate demonstrating the Card.Actions slot for interactive buttons. + +### Card.Actions Slot + +The Card.Actions slot provides interactive elements beyond the main card link: +- "Add to Cart" or "Get Now" buttons for products +- "Download" buttons for files +- "Register" buttons for events + +### Step 1: Create ProductActions Component + +Create `src/components/Actions/ProductAction.jsx`: + +```jsx +const ProductActions = ({ item }) => { + return ( + <> + + + ); +}; + +export default ProductActions; +``` + +### Step 2: Register ProductActions + +Update `src/config/settings.ts`: + +```typescript +import ProductActions from '../components/Actions/ProductAction'; + +export default function install(config: ConfigType) { + // ... previous configuration ... + + // Register ProductActions component + config.registerComponent({ + name: 'Actions', + component: ProductActions, + dependencies: ['product'], + }); + + return config; +} +``` + +### Step 3: Create ProductTemplate Listing Variation + +Create `src/components/blocks/Listing/ProductTemplate.jsx`: + +```jsx +import React from 'react'; +import PropTypes from 'prop-types'; +import ConditionalLink from '@plone/volto/components/manage/ConditionalLink/ConditionalLink'; +import Card from '@kitconcept/volto-light-theme/primitives/Card/Card'; +import { flattenToAppURL, isInternalURL } from '@plone/volto/helpers/Url/Url'; +import config from '@plone/volto/registry'; +import DefaultSummary from '@kitconcept/volto-light-theme/components/Summary/DefaultSummary'; +import cx from 'classnames'; + +const ProductTemplate = ({ items, linkTitle, linkHref, isEditMode }) => { + let link = null; + let href = linkHref?.[0]?.['@id'] || ''; + const PreviewImageComponent = config.getComponent('PreviewImage').component; + + if (isInternalURL(href)) { + link = ( + + {linkTitle || href} + + ); + } else if (href) { + link = {linkTitle || href}; + } + + return ( + <> +
+ {items.map((item) => { + const Summary = + config.getComponent({ + name: 'Summary', + dependencies: [item['@type']], + }).component || DefaultSummary; + + const Actions = config.getComponent({ + name: 'Actions', + dependencies: [item['@type']], + }).component; + + const showLink = !Summary.hideLink && !isEditMode; + + return ( +
+ + {item.image_field !== '' && ( + + )} + + + + + {Actions && } + + +
+ ); + })} +
+ + {link &&
{link}
} + + ); +}; + +ProductTemplate.propTypes = { + items: PropTypes.arrayOf(PropTypes.any).isRequired, + linkTitle: PropTypes.string, + linkHref: PropTypes.any, + isEditMode: PropTypes.bool, +}; + +export default ProductTemplate; +``` + +**Key Features:** +- Uses `config.getComponent()` to fetch registered Summary/Actions components by content type +- `showLink` ensures cards are only clickable when appropriate +- `isEditMode` disables navigation during editing +- Dynamically renders Actions only for content types that have them registered + +### Step 4: Register ProductTemplate + +Update `src/config/blocks.ts`: + +```typescript +import ProductTemplate from '../components/blocks/Listing/ProductTemplate'; + +export default function install(config: ConfigType) { + // ... previous configuration ... + + // Register ProductTemplate listing variation + config.blocks.blocksConfig.listing.variations = [ + ...(config.blocks.blocksConfig.listing.variations || []), + { + id: 'products', + title: 'Products', + template: ProductTemplate, + }, + ]; + + return config; +} +``` + +### Step 5: Add Styles + +Create `src/theme/blocks/_listing.scss`: + +```scss +.block.listing { + &.products { + .items { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 2rem; + + .card { + .card-actions { + padding: 1rem; + border-top: 1px solid var(--theme-high-contrast-color); + + .add-to-cart { + width: 100%; + padding: 0.75rem 1rem; + background-color: var(--accent-color); + color: var(--accent-foreground-color); + border: none; + border-radius: 4px; + font-weight: var(--font-bold); + cursor: pointer; + transition: opacity 0.2s; + + &:hover { + opacity: 0.9; + } + } + } + } + } + } +} +``` + +Import in `src/theme/_main.scss`: + +```scss +@import './blocks/hero'; +@import './blocks/relatedItems'; +@import './blocks/listing'; +@import './productSummary'; +@import './site'; +``` + +### Step 6: Test the Product Listing + +1. Create Product items with title, description, kicker (price), and images +2. Add a Listing block to a page +3. Select "Products" variation in block settings +4. Configure to show Product content type +5. Products display with image, summary, and "Get now" button + +## 4.4 Working with Slots + +VLT provides slots for extending the layout without component shadowing. Let's create a practical example: a newsletter signup component in the preFooter slot. + +### Available Slots + +- `aboveHeader`: Content above header +- `belowHeader`: Content below header +- `headerTools`: Header tools at top-right (includes Anontools by default) +- `preFooter`: Before footer +- `postFooter`: After footer +- `footer`: Main footer area +- `footerLinks`: Footer links section +- `followUs`: Social media section + +### Step 1: Create Newsletter Signup Component + +Create `src/components/NewsletterSignup/NewsletterSignup.jsx`: + +```jsx +import React, { useState } from 'react'; + +const NewsletterSignup = () => { + const [email, setEmail] = useState(''); + + const handleSubmit = (e) => { + e.preventDefault(); + // In a real implementation, this would submit to a newsletter service + console.log('Newsletter signup:', email); + alert(`Thanks for signing up with ${email}!`); + setEmail(''); + }; + + return ( +
+
+

Stay Updated

+

+ Subscribe to our newsletter for the latest updates and news. +

+
+ setEmail(e.target.value)} + placeholder="Enter your email" + required + className="newsletter-input" + /> + +
+
+
+ ); +}; + +export default NewsletterSignup; +``` + +### Step 2: Add Styles + +Create `src/theme/_newsletterSignup.scss`: + +```scss +.newsletter-signup { + background-color: var(--secondary-color); + color: var(--secondary-foreground-color); + padding: 4rem 2rem; + + .newsletter-container { + max-width: var(--default-container-width); + margin: 0 auto; + text-align: center; + } + + .newsletter-title { + font-size: 2rem; + margin-bottom: 1rem; + font-weight: var(--font-bold); + } + + .newsletter-description { + font-size: 1.125rem; + margin-bottom: 2rem; + opacity: 0.9; + } + + .newsletter-form { + display: flex; + gap: 1rem; + max-width: 500px; + margin: 0 auto; + flex-wrap: wrap; + justify-content: center; + + .newsletter-input { + flex: 1; + min-width: 250px; + padding: 0.75rem 1rem; + border: 1px solid var(--secondary-foreground-color); + font-size: 1rem; + background-color: var(--primary-color); + color: var(--primary-foreground-color); + + &:focus { + outline: 2px solid var(--accent-color); + outline-offset: 2px; + } + } + + .newsletter-button { + padding: 0.75rem 2rem; + background-color: var(--accent-color); + color: var(--accent-foreground-color); + border: none; + font-size: 1rem; + font-weight: var(--font-bold); + cursor: pointer; + transition: opacity 0.2s; + + &:hover { + opacity: 0.9; + } + + &:focus { + outline: 2px solid var(--accent-foreground-color); + outline-offset: 2px; + } + } + } +} +``` + +Import in `src/theme/_main.scss`: + +```scss +@import './blocks/hero'; +@import './blocks/relatedItems'; +@import './blocks/listing'; +@import './productSummary'; +@import './site'; +@import './newsletterSignup'; +``` + +### Step 3: Register the Component to the Slot + +In `src/config/settings.ts`: + +```typescript +import NewsletterSignup from '../components/NewsletterSignup/NewsletterSignup'; + +export default function install(config: ConfigType) { + // ... previous config ... + + config.registerSlotComponent({ + name: 'NewsletterSignup', + slot: 'preFooter', + component: NewsletterSignup, + }); + + return config; +} +``` + +The newsletter signup component will now appear before the footer on all pages, demonstrating how slots enable you to extend the layout without shadowing core components. + +## 4.5 Site Customization Behaviors + +VLT provides backend behaviors for site customization that you activated earlier. These behaviors enable fields for customizing the site without code changes. + +### Header Customization Options + +Through the Plone UI, you can customize: +- **Site logo**: Main logo in the top left +- **Complementary logo**: Second logo on the right side +- **Fat menu**: Enabled by default, can be disabled +- **Intranet header**: Alternative header for intranet sites +- **Actions**: Links at the top right + +### Theme Customization Options + +- Navigation text color +- Fat menu and breadcrumbs text color +- Fat menu background color +- Footer font color +- Footer background color + +### Footer Customization Options + +- **Footer links**: Additional links with title, URL, and new tab option +- **Footer logos**: List of logos with links and customizable size (small/large) and container width (default/layout) +- **Footer colophon text**: Customizable last line of footer + +## 4.6 Block Model v3 (opt-in) + +```{note} +Block Model v3 is a beta feature. It's recommended to only use it when all blocks in your registry are v3-compatible (indicated by banner in block's GitHub repository). +``` + +Block Model v3 introduces a unified container architecture that ensures consistent styling and spacing between View and Edit modes. This eliminates the previous issues where Edit mode appeared different from View mode. + +### Key Benefits + +- Consistent rendering across View and Edit modes +- Simplified CSS with standardized container structure +- Improved spacing control through block categories +- Reduced maintenance overhead + +### The Two-Container System + +Every block in Block Model v3 follows this structure: + +``` +┌────────────────────────────────────────────────────────────┐ +│ Main/Outer Container (.block.${type}.category-${category}) │ +│ • Full width (edge to edge) │ +│ • Background color & theme variables │ +│ • Vertical spacing via padding on BG color changes │ +│ │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ Secondary/Inner Container (.block-inner-container) │ │ +│ │ • Content width & horizontal centering │ │ +│ │ • Default vertical spacing between blocks │ │ +│ │ • Content alignment via CSS Grid │ │ +│ │ │ │ +│ │ [Block Content Here] │ │ +│ │ │ │ +│ └───────────────────────────────────────────────────────┘ │ +└────────────────────────────────────────────────────────────┘ +``` + +**Main/Outer Container:** +- Full width (extends edge to edge) +- Handles background colors and theme variables +- Uses padding (not margin) for vertical spacing +- CSS Classes: `.block.${type}.category-${category}` + +**Secondary/Inner Container:** +- Controls content width constraints +- Provides consistent inter-block spacing +- Supports content alignment through CSS Grid +- CSS Class: `.block-inner-container` + +### Enable Block Model v3 + +In your project's `src/config/settings.ts`: + +```typescript +export default function install(config: ConfigType) { + // Enable Block Model v3 globally + config.settings.blockModel = 3; + + // Define block categories for spacing + config.blocks.blocksConfig.slate.category = 'inline'; + config.blocks.blocksConfig.title.category = 'title'; + config.blocks.blocksConfig.__button.category = 'action'; + config.blocks.blocksConfig.gridBlock.category = 'cards'; + + return config; +} +``` + +### Block Categories + +Block categories determine spacing relationships between adjacent blocks. Categories are available at `config.blocks.blocksConfig.[$type].category`. + +Vertical spacing between blocks is provided by the **upper block**: +- Block content should be flush with top of container +- Bottom padding creates space for following block +- Different category combinations may have specific spacing adjustments + +### View Mode Structure + +```jsx +
+
+ {View component} +
+
+``` + +### Edit Mode Structure + +```jsx +
+
+ {Edit component} +
+
+ {/* Delete block button, move block buttons, etc. */} +
+
+``` + +Notice how the actual block content remains identical in both modes, while the framework containers handle all the differences in layout and editing functionality. + +--- diff --git a/docs/customizing-volto-light-theme/block-development-widgets.md b/docs/customizing-volto-light-theme/block-development-widgets.md new file mode 100644 index 000000000..a363ee1fa --- /dev/null +++ b/docs/customizing-volto-light-theme/block-development-widgets.md @@ -0,0 +1,647 @@ +--- +myst: + html_meta: + "description": "Block Development, Widgets & Integration" + "property=og:description": "Block Development, Widgets & Integration" + "property=og:title": "Block Development, Widgets & Integration" + "keywords": "Plone, Volto, Training, Volto Light Theme" +--- + +# 3. Block Development, Widgets & Integration + +## 3.1 Understanding VLT Widgets + +VLT provides powerful widgets for block configuration that work with the StyleWrapper system. + +### BlockWidth Widget + +Controls the content width of blocks: + +```javascript +{ + widget: 'blockWidth', + title: 'Block Width', + default: 'default', +} +``` + +### BlockAlignment Widget + +Controls content alignment within blocks: + +```javascript +{ + widget: 'blockAlignment', + title: 'Alignment', + default: 'center', +} +``` + +### ThemeColorSwatch Widget + +Allows selection from configured themes stored in `config.blocks.themes`: + +```javascript +{ + widget: 'themeColorSwatch', + title: 'Color Theme', + colors: config.blocks.themes, +} +``` + +### ObjectList Widget + +Allows introducing a list of ordered objects with drag and drop: + +```javascript +{ + widget: 'object_list', + title: 'Items', + schemaName: 'mySchemaName', +} +``` + +## 3.2 Creating a Custom Hero Block + +Let's build a hero block step by step, starting with a basic implementation and then enhancing it with VLT widgets. + +### Step 1: Create Basic Block Schema + +Create `src/components/blocks/myHero/schema.js`: + +```javascript +import { defineMessages } from 'react-intl'; + +const messages = defineMessages({ + hero: { + id: 'Hero', + defaultMessage: 'Hero', + }, + title: { + id: 'Title', + defaultMessage: 'Title', + }, + subtitle: { + id: 'Subtitle', + defaultMessage: 'Subtitle', + }, + backgroundImage: { + id: 'Background Image', + defaultMessage: 'Background Image', + }, +}); + +const heroBlockSchema = (props) => { + const { intl } = props; + + return { + title: intl.formatMessage(messages.hero), + fieldsets: [ + { + id: 'default', + title: 'Default', + fields: ['title', 'subtitle'], + }, + { + id: 'design', + title: 'Design', + fields: ['backgroundImage'], + }, + ], + properties: { + title: { + title: intl.formatMessage(messages.title), + type: 'string', + }, + subtitle: { + title: intl.formatMessage(messages.subtitle), + type: 'string', + }, + backgroundImage: { + title: intl.formatMessage(messages.backgroundImage), + widget: 'object_browser', + mode: 'image', + allowExternals: false, + }, + }, + required: [], + }; +}; + +export { heroBlockSchema }; +``` + +### Step 2: Create View Component + +Create `src/components/blocks/myHero/View.jsx`: + +```jsx +import React from 'react'; +import { withBlockExtensions } from '@plone/volto/helpers'; +import cx from 'classnames'; +import config from '@plone/volto/registry'; +import { flattenToAppURL, isInternalURL } from '@plone/volto/helpers/Url/Url'; + +const HeroView = (props) => { + const { data, className, style } = props; + const { title, subtitle, backgroundImage } = data; + + let renderedImage = null; + if (data.backgroundImage) { + let Image = config.getComponent('Image').component; + if (Image) { + renderedImage = ( + + ); + } else { + renderedImage = ( + + ); + } + } + + return ( +
+
+
+ {title &&

{title}

} + {subtitle &&

{subtitle}

} +
+ + {backgroundImage && ( +
{renderedImage}
+ )} +
+
+ ); +}; + +export default withBlockExtensions(HeroView); +``` + +### Step 3: Create Edit Component + +Create `src/components/blocks/myHero/Edit.tsx`: + +```tsx +import React from 'react'; +import { useIntl } from 'react-intl'; +import SidebarPortal from '@plone/volto/components/manage/Sidebar/SidebarPortal'; +import { BlockDataForm } from '@plone/volto/components/manage/Form'; +import { heroBlockSchema } from './schema'; +import HeroView from './View'; +import type { BlockEditProps } from '@plone/types'; + +const HeroEdit = (props: BlockEditProps) => { + const { selected, onChangeBlock, block, data } = props; + const intl = useIntl(); + + return ( + <> + + + { + onChangeBlock(block, { + ...data, + [id]: value, + }); + }} + /> + + + ); +}; + +export default HeroEdit; +``` + +### Step 4: Register the Basic Block + +Update `src/config/blocks.ts`: + +```typescript +import type { ConfigType } from '@plone/registry'; +import HeroView from '../components/blocks/myHero/View'; +import HeroEdit from '../components/blocks/myHero/Edit'; +import { heroBlockSchema } from '../components/blocks/myHero/schema.js'; +import heroSVG from '@plone/volto/icons/hero.svg'; + +export default function install(config: ConfigType) { + // ... block themes configuration ... + + // Register Hero Block + config.blocks.blocksConfig.hero = { + id: 'hero', + title: 'Hero', + icon: heroSVG, + group: 'common', + view: HeroView, + edit: HeroEdit, + restricted: false, + mostUsed: true, + blockSchema: heroBlockSchema, + sidebarTab: 1, + category: 'hero', + }; + + return config; +} +``` + +### Step 5: Add Basic Block Styles + +Create `src/theme/blocks/_hero.scss`: + +```scss +.block.hero { + position: relative; + display: flex; + align-items: center; + justify-content: center; + background-size: cover; + background-position: center; + + .hero-content { + .hero-image-wrapper { + img { + aspect-ratio: var(--image-aspect-ratio, 16/9); + opacity: 0.8; + } + } + + .hero-text { + position: absolute; + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + padding: 4rem; + z-index: 2; + + .hero-title { + font-size: 5rem; + font-weight: var(--font-bold); + margin-bottom: var(--space-3); + line-height: 1; + } + + .hero-subtitle { + font-size: 3rem; + margin-bottom: var(--space-6); + opacity: 0.95; + line-height: 1; + } + } + } +} +``` + +Import in `src/theme/_main.scss`: + +```scss +@import './blocks/hero'; +@import './site'; +``` + +### Step 6: Enhance with VLT Widgets + +Now let's add VLT's powerful widgets to control block width and alignment. Update the schema file to add the schema enhancer. + +Update `src/components/blocks/myHero/schema.js`: + +```javascript +import { defineMessages } from 'react-intl'; + +const messages = defineMessages({ + hero: { + id: 'Hero', + defaultMessage: 'Hero', + }, + title: { + id: 'Title', + defaultMessage: 'Title', + }, + subtitle: { + id: 'Subtitle', + defaultMessage: 'Subtitle', + }, + backgroundImage: { + id: 'Background Image', + defaultMessage: 'Background Image', + }, + blockWidth: { + id: 'Block Width', + defaultMessage: 'Block Width', + }, + textAlignment: { + id: 'Text Alignment', + defaultMessage: 'Text Alignment', + }, +}); + +const heroBlockSchema = (props) => { + const { intl } = props; + + return { + title: intl.formatMessage(messages.hero), + fieldsets: [ + { + id: 'default', + title: 'Default', + fields: ['title', 'subtitle'], + }, + { + id: 'design', + title: 'Design', + fields: ['backgroundImage'], + }, + ], + properties: { + title: { + title: intl.formatMessage(messages.title), + type: 'string', + }, + subtitle: { + title: intl.formatMessage(messages.subtitle), + type: 'string', + }, + backgroundImage: { + title: intl.formatMessage(messages.backgroundImage), + widget: 'object_browser', + mode: 'image', + allowExternals: false, + }, + }, + required: [], + }; +}; + +// Schema enhancer to add VLT widget styling fields +const heroSchemaEnhancer = ({ formData, schema, intl }) => { + // Add custom fields to the styling schema at the beginning + schema.properties.styles.schema.fieldsets[0].fields = [ + 'blockWidth:noprefix', + 'align', + ...schema.properties.styles.schema.fieldsets[0].fields, + ]; + + schema.properties.styles.schema.properties['blockWidth:noprefix'] = { + widget: 'blockWidth', + title: intl.formatMessage(messages.blockWidth), + default: 'default', + }; + + schema.properties.styles.schema.properties.align = { + widget: 'blockAlignment', + title: intl.formatMessage(messages.textAlignment), + default: 'center', + }; + + return schema; +}; + +export { heroBlockSchema, heroSchemaEnhancer }; +``` + +### Step 7: Update Block Registration with Schema Enhancer + +Update `src/config/blocks.ts` to include the schema enhancer: + +```typescript +import type { ConfigType } from '@plone/registry'; +import HeroView from '../components/blocks/myHero/View'; +import HeroEdit from '../components/blocks/myHero/Edit'; +import { + heroBlockSchema, + heroSchemaEnhancer, +} from '../components/blocks/myHero/schema.js'; +import { composeSchema } from '@plone/volto/helpers/Extensions'; +import { defaultStylingSchema } from '@kitconcept/volto-light-theme/components/Blocks/schema'; +import heroSVG from '@plone/volto/icons/hero.svg'; + +export default function install(config: ConfigType) { + // ... block themes configuration ... + + // Register Hero Block + config.blocks.blocksConfig.hero = { + id: 'hero', + title: 'Hero', + icon: heroSVG, + group: 'common', + view: HeroView, + edit: HeroEdit, + restricted: false, + mostUsed: true, + blockSchema: heroBlockSchema, + schemaEnhancer: composeSchema(defaultStylingSchema, heroSchemaEnhancer), + sidebarTab: 1, + category: 'hero', + }; + + return config; +} +``` + +### Step 8: Update Styles to Use Widget Values + +Update `src/theme/blocks/_hero.scss` to use the CSS custom properties set by the widgets: + +```scss +.block.hero { + position: relative; + display: flex; + align-items: center; + justify-content: center; + background-size: cover; + background-position: center; + background-color: var(--theme-color); + color: var(--theme-foreground-color); + max-width: var(--block-width) !important; + margin-left: auto; + margin-right: auto; + + .hero-content { + .hero-image-wrapper { + img { + aspect-ratio: var(--image-aspect-ratio, 16/9); + opacity: 0.8; + } + } + + .hero-text { + position: absolute; + display: flex; + flex-direction: column; + align-items: var(--align--block-alignment); + width: 100%; + height: 100%; + padding: 4rem; + z-index: 2; + + .hero-title { + font-size: 5rem; + font-weight: var(--font-bold); + margin-bottom: var(--space-3); + line-height: 1; + } + + .hero-subtitle { + font-size: 3rem; + margin-bottom: var(--space-6); + opacity: 0.95; + line-height: 1; + } + } + } +} +``` + +## 3.3 Integrating Third-Party Blocks + +Let's learn how to integrate the `@plone-collective/volto-relateditems-block` into VLT. + +### Install the Block + +```bash +pnpm install @plone-collective/volto-relateditems-block@latest +``` + +Add it to your `package.json` addons (before VLT): + +```json +"addons": [ + "@eeacms/volto-accordion-block", + "@kitconcept/volto-button-block", + "@kitconcept/volto-heading-block", + "@kitconcept/volto-highlight-block", + "@kitconcept/volto-introduction-block", + "@kitconcept/volto-separator-block", + "@kitconcept/volto-slider-block", + "@plone-collective/volto-relateditems-block", + "@kitconcept/volto-light-theme" +], +``` + +### Enhance the Block Schema + +To add the Block Width widget to the Related Items block, create a schema enhancer file. + +Create `src/components/blocks/relatedItems/schemaEnhancer.js`: + +```javascript +import { defineMessages } from 'react-intl'; + +const messages = defineMessages({ + blockWidth: { + id: 'Block Width', + defaultMessage: 'Block Width', + }, +}); + +const relatedItemsSchemaEnhancer = ({ formData, schema, intl }) => { + schema.properties.styles.schema.fieldsets[0].fields = [ + 'blockWidth:noprefix', + ...schema.properties.styles.schema.fieldsets[0].fields, + ]; + + schema.properties.styles.schema.properties['blockWidth:noprefix'] = { + widget: 'blockWidth', + title: intl.formatMessage(messages.blockWidth), + default: 'default', + filterActions: ['narrow', 'default'], + }; + + return schema; +}; + +export default relatedItemsSchemaEnhancer; +``` + +Then update `src/config/blocks.ts` to register the enhancer: + +```typescript +import { composeSchema } from '@plone/volto/helpers/Extensions'; +import { defaultStylingSchema } from '@kitconcept/volto-light-theme/components/Blocks/schema'; +import relatedItemsSchemaEnhancer from '../components/blocks/relatedItems/schemaEnhancer'; + +export default function install(config: ConfigType) { + // ... previous configuration ... + + config.blocks.blocksConfig.relatedItems = { + ...config.blocks.blocksConfig.relatedItems, + schemaEnhancer: composeSchema(defaultStylingSchema, relatedItemsSchemaEnhancer), + }; + + return config; +} +``` + +### Add Custom Styles + +Create `src/theme/blocks/_relatedItems.scss`: + +```scss +.block.relatedItems { + max-width: var(--block-width); + margin-right: auto; + margin-left: auto; + + .inner-container { + background: var(--theme-high-contrast-color); + padding: 3rem; + + h2.headline { + color: var(--theme-foreground-color); + } + + ul { + color: var(--theme-foreground-color); + li a { + color: var(--link-foreground-color); + } + } + } + + +} + +``` + +Import it in `_main.scss`: + +```scss +@import './blocks/hero'; +@import './blocks/relatedItems'; +@import './site'; +``` + +--- diff --git a/docs/customizing-volto-light-theme/concepts.md b/docs/customizing-volto-light-theme/concepts.md index 91eba4414..70ef7e4cd 100644 --- a/docs/customizing-volto-light-theme/concepts.md +++ b/docs/customizing-volto-light-theme/concepts.md @@ -1,35 +1,38 @@ --- myst: html_meta: - "description": "Concepts" - "property=og:description": "Concepts" - "property=og:title": "Concepts" + "description": "Foundation, Concepts & Project Setup" + "property=og:description": "Foundation, Concepts & Project Setup" + "property=og:title": "Foundation, Concepts & Project Setup" "keywords": "Plone, Volto, Training, Volto Light Theme" --- +# 1. Foundation, Concepts & Project Setup -# Volto Light Theme Concepts +## 1.1 Volto Light Theme Core Concepts Volto Light Theme (VLT) is a customizable theme built for the Volto frontend of the Plone CMS. It provides a foundation that aims to solve many common design challenges, while remaining flexible enough for customization. It's particularly valuable because it is based on real-world experience, while simultaneously embodying the Volto vision for the future. This module will help you understand the core concepts in VLT and create a mental map of its parts. -## Base Styling +### Base Styling -VLT is designed with simplicity and a minimal aesthetic in mind. Consistency, accessibility, and intuitiveness are what drive the development and improvements for VLT. +VLT is designed with simplicity and a minimal aesthetic in mind. The three core principles are: +- **Consistency**: Predictable design patterns across all components +- **Accessibility**: WCAG compliant with contrast checkers and semantic HTML +- **Intuitiveness**: Clear visual hierarchy and user-friendly interfaces -## Customizable Variables +### Customizable Variables -VLT offers a set of CSS custom properties (variables) that allow developers to customize various design elements, such as: - -- Colors -- Spatial relationships -- Layouts +VLT offers a set of CSS custom properties (variables) that allow developers to customize various design elements: +- **Colors**: Using paired foreground/background color system +- **Spatial relationships**: Container widths and spacing scales +- **Layouts**: Three-width container system These variables can be easily overridden in your project to match the desired visual identity. -## Colors +### Color System -The color system is designed so that colors work in couples: a "background color" and a "foreground color". The "foreground color" is often called "text color" in other systems, but since we want to use this value for more than text—like icons or borders, this works better as a generic term. Colors that do not specify "foreground" in the name are meant to be background colors. +The color system is designed so that colors work in couples: a "background color" and a "foreground color". The "foreground color" is often called "text color" in other systems, but since we want to use this value for more than text—like icons or borders—this works better as a generic term. Colors that do not specify "foreground" in the name are meant to be background colors. -The main color properties for a project using VLT are the following: +The main color properties for a project using VLT are: ```scss --primary-color: #fff; @@ -42,9 +45,9 @@ The main color properties for a project using VLT are the following: --accent-foreground-color: #000; ``` -### Semantic color properties +### Semantic Color Properties -As an additional layer on top of the main color properties, we have set in place some semantic custom properties for the basic layout sections. As a default they use the values from the main color variables, but they can be detached if desired by setting new color values. However, we feel that leaving these color relationships as they are helps create a cohesive final design. The semantic layer of properties includes the following: +As an additional layer on top of the main color properties, we have set in place some semantic custom properties for the basic layout sections. As a default they use the values from the main color variables, but they can be detached if desired by setting new color values. However, leaving these color relationships as they are helps create a cohesive final design: ```scss // Header @@ -71,32 +74,64 @@ As an additional layer on top of the main color properties, we have set in place --link-foreground-color: var(--link-color); ``` -## Block width +### Block Themes + +VLT includes a block theme system that enables individual blocks to use distinct color palettes. These themes are configured in `config.blocks.themes` and applied through the StyleWrapper system at runtime. + +**The four core block theme variables:** + +- `--theme-color`: Primary background color — the most visible color in the block +- `--theme-high-contrast-color`: Secondary background color for nested elements (e.g., cards within a block) to create visual separation from the main background +- `--theme-foreground-color`: Default text and icon color +- `--theme-low-contrast-foreground-color`: Subdued text color for secondary content like placeholders or helper text + +While the system can be extended with non-color CSS properties, the default four variables establish the color foundation for each theme. + +**Example configuration:** + +```typescript +config.blocks.themes = [ + { + style: { + '--theme-color': '#fff', + '--theme-high-contrast-color': '#ecebeb', + '--theme-foreground-color': '#000', + '--theme-low-contrast-foreground-color': '#555555', + }, + name: 'default', + label: 'Default', + }, + { + style: { + '--theme-color': '#ecebeb', + '--theme-high-contrast-color': '#fff', + '--theme-foreground-color': '#000', + '--theme-low-contrast-foreground-color': '#555555', + }, + name: 'grey', + label: 'Grey', + }, +]; +``` -VLT now uses a standard block width definition as well. Currently, this is done via a `widget` component, `BlockWidthWidget`, that will be ported to Volto core in the near future. This component stores the value of the custom property `--block-width` so that it can be used by the StyleWrapper when injecting styles into the markup. +Users select block themes through the `themeColorSwatch` widget in the block sidebar. This widget renders colored buttons using each theme's `--theme-color` value, allowing visual theme selection. -The three-width layout system considers the following variables: +### Container Width System -```scss ---layout-container-width: #{$layout-container-width}; // for major elements like headers, & large Blocks like the `volto-slider-block`. ---default-container-width: #{$default-container-width}; // a balanced content presentation for most Blocks. ---narrow-container-width: #{$narrow-container-width}; // optimal readability. -``` - -The default values map the container width SCSS variables, which have existed in the VLT ecosystem since versions < 6.0.0-alpha.0: +VLT uses three types of container widths: ```scss -// Container widths -$layout-container-width: 1440px !default; -$default-container-width: 940px !default; -$narrow-container-width: 620px !default; +// Three-width layout system +--layout-container-width: 1440px; // for major elements like headers & large Blocks +--default-container-width: 940px; // balanced content presentation for most Blocks +--narrow-container-width: 620px; // optimal readability for text ``` -## Block alignment +The VLT `BlockWidthWidget` stores the value of the custom property `--block-width` so that it can be used by the StyleWrapper when injecting styles into the markup. -As part of the effort to generalize behaviors, another VLT `widget` is the `BlockAlignmentWidget`, which also takes advantage of the StyleWrapper by setting the `--block-alignment` property. +### Block Alignment -The three default options are: +The `BlockAlignmentWidget` takes advantage of the StyleWrapper by setting the `--block-alignment` property. The three default options are: ```scss --align-left: start; @@ -104,6 +139,148 @@ The three default options are: --align-right: end; ``` -## Conclusion +## 1.2 Create a New Project with Cookieplone + +We recommend creating your Plone project with **Cookieplone**. Our comprehensive documentation provides step-by-step guidance to help you get started. For detailed installation instructions, visit our [Cookieplone guide](https://6.docs.plone.org/install/create-project-cookieplone.html). + +## 1.3 Installing Volto Light Theme + +### Step 1: Install VLT and Recommended Block Add-ons + +Navigate to the `frontend/packages/my-vlt-project` folder and install VLT: + +```bash +pnpm install @kitconcept/volto-light-theme@latest +``` + +Volto Light Theme comes with several pre-configured add-ons that provide basic blocks for your website. If you'd like to include them, you can add them in the `addons` section in your {file}`package.json`, but this is not required. + +Here is the list of recommended addons to install, including VLT, which should be the last element: + +```json +"addons": [ + "@eeacms/volto-accordion-block", + "@kitconcept/volto-button-block", + "@kitconcept/volto-heading-block", + "@kitconcept/volto-highlight-block", + "@kitconcept/volto-introduction-block", + "@kitconcept/volto-separator-block", + "@kitconcept/volto-slider-block", + "@kitconcept/volto-light-theme" +], +``` + +While in your project package folder, add VLT and the block addons to the `addons` list in your `package.json`: + +```json +{ + "name": "my-vlt-project", + "addons": [ + "@eeacms/volto-accordion-block", + "@kitconcept/volto-button-block", + "@kitconcept/volto-heading-block", + "@kitconcept/volto-highlight-block", + "@kitconcept/volto-introduction-block", + "@kitconcept/volto-separator-block", + "@kitconcept/volto-slider-block", + "@kitconcept/volto-light-theme" + ] +} +``` + +**Important:** VLT must be the last addon in the list to ensure proper style cascade. Your project addon will still be the last applied if defined in `volto.config.js`. + +### Step 2: Configure VLT as the Theme Provider + +Open the `volto.config.js` file in your `frontend` folder and modify it as shown below: + +```javascript +const addons = ['my-vlt-project']; +const theme = '@kitconcept/volto-light-theme'; + +module.exports = { + addons, + theme, +}; +``` + +You'll need to restart your Plone frontend to see the changes. + +That's it! Your project should now be using Volto Light Theme with its additional blocks and components. + +### Step 3: Install Backend Package + +In your backend folder, install the Python package for site customization behaviors. + +Edit `backend/pyproject.toml` and add to the dependencies array: + +``` +dependencies = [ + "Products.CMFPlone==6.1.3", + "plone.api", + "plone.restapi", + "plone.volto", + "kitconcept.voltolighttheme==7.3.0", +] +``` + +### Step 4: Install the Backend Add-on + +Start your development environment: + +```bash +# Terminal 1 - Backend +make backend-start + +# Terminal 2 - Frontend +make frontend-start +``` + +Once the frontend is running: + +1. Go to http://localhost:3000/controlpanel/addons +2. Find "Volto Light Theme" in the list +3. Click "Install" + +### Step 5: Activate Behaviors for Plone Site + +To enable site customization through the UI: + +1. Go to http://localhost:3000/controlpanel/dexterity-types/Plone%20Site +2. In the "Behaviors" tab, activate the desired behaviors +3. Click "Save" + +Now your project should have the VLT Site configurations available. + +## 1.4 File Structure Setup + +Let's set up the recommended file structure. In your project add-on's `src` folder, create the following structure: + +```console +src/ +├── components/ +│ └── blocks/ +├── config/ +│ ├── settings.ts +│ └── blocks.ts +├── index.ts +└── theme/ + ├── blocks/ + ├── _main.scss + └── _site.scss +``` + +Create the files: + +```bash +cd src +mkdir -p components/blocks config theme/blocks +touch config/settings.ts config/blocks.ts +touch index.ts +touch theme/_main.scss theme/_site.scss +``` + +Remember that if you add new files to your project, it will be necessary to restart your Plone frontend. + -VLT provides a robust foundation for Plone CMS frontend development through this framework of customizable variables and standardized block controls. Through these core concepts, VLT strikes a balance between maintaining consistency and flexibility, allowing developers to create cohesive designs while still having the freedom to customize elements to match their specific project needs. +--- \ No newline at end of file diff --git a/docs/customizing-volto-light-theme/creating-new-project.md b/docs/customizing-volto-light-theme/creating-new-project.md deleted file mode 100644 index 9cc7fef69..000000000 --- a/docs/customizing-volto-light-theme/creating-new-project.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -myst: - html_meta: - "description": "Create a new project with Plone and Volto" - "property=og:description": "Create a new project with Plone and Volto" - "property=og:title": "Create a new project with Plone and Volto" - "keywords": "Plone, Volto, Training, Theme, Footer" ---- - -# Create a new project with Plone and Volto -We recommend creating your Plone project with **Cookieplone**. Our comprehensive documentation provides step-by-step guidance to help you get started. For detailed installation instructions, visit our [Cookieplone guide](https://6.docs.plone.org/install/create-project-cookieplone.html). \ No newline at end of file diff --git a/docs/customizing-volto-light-theme/design-system-implementation.md b/docs/customizing-volto-light-theme/design-system-implementation.md new file mode 100644 index 000000000..71cabcf3e --- /dev/null +++ b/docs/customizing-volto-light-theme/design-system-implementation.md @@ -0,0 +1,453 @@ +--- +myst: + html_meta: + "description": "Design system implementation & theming" + "property=og:description": "Design system implementation & theming" + "property=og:title": "Design system implementation & theming" + "keywords": "Plone, Volto, Training, Theme, Footer" +--- + +# 2. Design System Implementation & Theming + +## 2.1 Extracting Design Tokens + +When working with a given design, systematically extract design decisions. Identify: + +### Color Extraction Checklist + +1. **Primary Colors** + - Background color + - Text color on primary + +2. **Secondary Colors** + - Background color + - Text color on secondary + +3. **Accent Colors** + - Highlight color + - Text color on accent + +4. **Semantic Colors** + - Link colors + +### Typography Extraction + +Look for: +- Font families +- Line heights +- Font weights + + +## 2.2 Implementing Your Design System + +VLT has migrated to use standardized color definitions. These use CSS properties that are injected at runtime in the right places, so your CSS can adapt to use them generically. The resulting CSS is simpler, and there's no need to define class names for each color definition. + +### Step 1: Create _site.scss + +In `src/theme/_site.scss`, define your color variables and custom properties: + +```scss +@font-face { + font-family: 'Chakra Petch'; + src: url('./fonts/Chakra_Petch/ChakraPetch-Regular.ttf') format('truetype'); +} + +:root { + // Extract these from your design + --accent-color: #3b5759; + --accent-foreground-color: #fff; + --secondary-color: #afcac8; + + // Typography + --text-base: 1.15rem; + --custom-main-font: 'Chakra Petch', sans-serif; + --line-height-factor: 1.5; + + // Gradients for header/footer + --header-background: linear-gradient( + -3deg, + var(--background, #fff) 0%, + color-mix(in oklab, var(--secondary-color) 1%, var(--background, #fff)) 20%, + color-mix(in oklab, var(--secondary-color) 3%, var(--background, #fff)) 35%, + color-mix(in oklab, var(--secondary-color) 10%, var(--background, #fff)) 50%, + color-mix(in oklab, var(--secondary-color) 30%, var(--background, #fff)) 65%, + color-mix(in oklab, var(--secondary-color) 65%, var(--background, #fff)) 80%, + color-mix(in oklab, var(--secondary-color) 90%, var(--background, #fff)) 92%, + var(--secondary-color) 100% + ); + + --footer-background: radial-gradient( + ellipse 100% 100% at 50% 100%, + var(--secondary-color) 0%, + var(--secondary-color) 20%, + color-mix(in oklab, var(--secondary-color) 85%, var(--background, #fff)) 30%, + color-mix(in oklab, var(--secondary-color) 65%, var(--background, #fff)) 40%, + color-mix(in oklab, var(--secondary-color) 45%, var(--background, #fff)) 50%, + color-mix(in oklab, var(--secondary-color) 30%, var(--background, #fff)) 60%, + color-mix(in oklab, var(--secondary-color) 18%, var(--background, #fff)) 70%, + color-mix(in oklab, var(--secondary-color) 10%, var(--background, #fff)) 80%, + color-mix(in oklab, var(--secondary-color) 4%, var(--background, #fff)) 90%, + var(--background, #fff) 100% + ); + + --fatmenu-foreground: #fff; + + // Container widths (customized) + --default-container-width: 1440px; + --layout-container-width: 90%; + + // Link color + --link-foreground-color: #157a7a; + + // Breadcrumbs + --breadcrumbs-background: var(--background, #fff); + --breadcrumbs-foreground: var(--secondary-foreground-color, #3b5759); + + .breadcrumbs { + border-bottom: 1px solid #afcac8; + } + + // Block-specific styles + #page-document, + #page-edit, + #page-add { + .blocks-group-wrapper:first-child { + padding-top: 0; + + } + .block { + + &.__button { + .button { + button { + padding: 1rem; + } + } + } + + &.__button { + .ui.button:hover { + --theme-color: #fff; + } + } + + &.slider { + .teaser-item-title { + background: rgba(255, 255, 255, 0.1); + color: var(--theme-foreground-color) !important; + backdrop-filter: blur(20px) saturate(110%); + -webkit-backdrop-filter: blur(20px) saturate(180%); + box-shadow: + 0 8px 32px 0 rgba(31, 135, 125, 0.1), + inset 0 0 0 1px rgba(255, 255, 255, 0.1); + } + } + + &.gridBlock { + .four { + .slate:not(.inner) { + padding: 2.5rem; + padding-top: 4rem !important; + backdrop-filter: blur(20px) saturate(110%); + -webkit-backdrop-filter: blur(20px) saturate(180%); + box-shadow: + 0 8px 32px 0 rgba(31, 135, 125, 0.1), + inset 0 0 0 1px rgba(255, 255, 255, 0.1); + } + } + } + + &.teaser { + .card { + .card-inner { + .card-summary { + padding: $spacing-large; + } + } + } + } + } + + + } +} + +#sidebar { + .color-swatch-widget { + .buttons button.teal { + background: linear-gradient(135deg, var(--secondary-color) 0%, #fff 100%); + } + } +} +``` + +### Step 2: Configure Block Themes + +In `src/config/blocks.ts`, define block themes: + +```typescript +import type { ConfigType } from '@plone/registry'; + +export default function install(config: ConfigType) { + // Block palettes + config.blocks.themes = [ + { + style: { + '--theme-color': 'white', + '--theme-high-contrast-color': '#bbd1d0', + '--theme-foreground-color': 'black', + '--theme-low-contrast-foreground-color': '#555555', + }, + name: 'default', + label: 'Default', + }, + { + style: { + '--theme-color': `/* White edge bleed for continuity */ + linear-gradient(180deg, + rgba(255, 255, 255, 0.9) 0%, + rgba(255, 255, 255, 0.6) 8%, + rgba(255, 255, 255, 0.3) 12%, + transparent 18%, + transparent 82%, + rgba(255, 255, 255, 0.3) 88%, + rgba(255, 255, 255, 0.6) 92%, + rgba(255, 255, 255, 0.9) 100%), + + /* Top white highlights - asymmetric cluster */ + radial-gradient(ellipse 900px 650px at 12% 8%, + rgba(255, 255, 255, 0.38) 0%, + rgba(243, 251, 251, 0.26) 28%, + rgba(228, 244, 244, 0.14) 52%, + transparent 72%), + + radial-gradient(ellipse 750px 850px at 6% 22%, + rgba(255, 255, 255, 0.34) 0%, + rgba(238, 249, 249, 0.2) 32%, + rgba(220, 240, 240, 0.1) 58%, + transparent 78%), + + radial-gradient(ellipse 820px 580px at 88% 12%, + rgba(255, 255, 255, 0.42) 0%, + rgba(246, 252, 252, 0.28) 30%, + rgba(232, 246, 246, 0.15) 54%, + transparent 74%), + + /* Bottom white highlights - dispersed asymmetrically */ + radial-gradient(ellipse 980px 620px at 82% 88%, + rgba(255, 255, 255, 0.4) 0%, + rgba(244, 251, 251, 0.27) 26%, + rgba(230, 245, 245, 0.13) 50%, + transparent 73%), + + radial-gradient(ellipse 680px 780px at 18% 90%, + rgba(255, 255, 255, 0.35) 0%, + rgba(240, 250, 250, 0.22) 34%, + rgba(225, 242, 242, 0.11) 60%, + transparent 80%), + + radial-gradient(ellipse 720px 520px at 68% 94%, + rgba(255, 255, 255, 0.3) 0%, + rgba(235, 247, 247, 0.17) 38%, + transparent 68%), + + /* Color hotspot 1 - upper right quadrant */ + radial-gradient(ellipse 850px 750px at 70% 35%, + rgba(175, 202, 200, 0.5) 0%, + rgba(180, 206, 204, 0.38) 25%, + rgba(190, 212, 210, 0.24) 45%, + rgba(200, 220, 218, 0.12) 65%, + transparent 85%), + + /* Color hotspot 2 - lower left quadrant */ + radial-gradient(ellipse 920px 800px at 25% 65%, + rgba(165, 195, 193, 0.45) 0%, + rgba(175, 202, 200, 0.34) 28%, + rgba(185, 210, 208, 0.22) 50%, + rgba(195, 215, 213, 0.11) 70%, + transparent 87%), + + /* Color hotspot 3 - center right */ + radial-gradient(circle 700px at 78% 52%, + rgba(170, 198, 196, 0.42) 0%, + rgba(180, 205, 203, 0.3) 30%, + rgba(192, 214, 212, 0.18) 55%, + rgba(205, 222, 220, 0.09) 75%, + transparent 90%), + + /* Organic flow connecting hotspots */ + radial-gradient(ellipse 1100px 900px at 45% 48%, + rgba(175, 202, 200, 0.28) 0%, + rgba(185, 210, 208, 0.18) 35%, + rgba(195, 216, 214, 0.1) 60%, + rgba(205, 222, 220, 0.05) 78%, + transparent 90%), + + /* Additional scattered color pockets */ + radial-gradient(circle 550px at 15% 40%, + rgba(172, 198, 196, 0.32) 0%, + rgba(182, 207, 205, 0.2) 38%, + rgba(195, 216, 214, 0.1) 68%, + transparent 85%), + + radial-gradient(ellipse 680px 580px at 88% 68%, + rgba(178, 203, 201, 0.35) 0%, + rgba(188, 210, 208, 0.22) 35%, + rgba(200, 218, 216, 0.11) 65%, + transparent 82%), + + radial-gradient(ellipse 620px 720px at 52% 28%, + rgba(168, 196, 194, 0.3) 0%, + rgba(180, 205, 203, 0.18) 40%, + rgba(195, 215, 213, 0.08) 70%, + transparent 88%), + + radial-gradient(circle 480px at 35% 78%, + rgba(175, 200, 198, 0.28) 0%, + rgba(188, 210, 208, 0.15) 42%, + transparent 75%), + + /* Asymmetrical accent flows */ + conic-gradient(from 125deg at 28% 44%, + transparent 0deg, + rgba(178, 203, 201, 0.18) 48deg, + rgba(185, 210, 208, 0.14) 95deg, + transparent 145deg, + rgba(172, 198, 196, 0.12) 235deg, + transparent 285deg), + + /* Base gradient foundation - subtle */ + linear-gradient(182deg, + #ffffff 0%, + #f9fcfc 10%, + #ebf4f3 22%, + #d8e6e5 35%, + #c5d8d6 45%, + #b8cece 52%, + #c5d8d6 59%, + #d8e6e5 69%, + #ebf4f3 82%, + #f9fcfc 92%, + #ffffff 100%)`, + '--theme-high-contrast-color': 'rgba(255, 255, 255, 0.1)', + '--theme-foreground-color': 'black', + '--theme-low-contrast-foreground-color': '#555555', + }, + name: 'teal', + label: 'Teal', + }, + ]; + + return config; +} +``` + +### Step 3: Configure Settings + +In `src/config/settings.ts`: + +```typescript +import type { ConfigType } from '@plone/registry'; +import installBlocks from './blocks'; + +export default function install(config: ConfigType) { + // Language settings + config.settings.isMultilingual = false; + config.settings.supportedLanguages = ['en']; + config.settings.defaultLanguage = 'en'; + + installBlocks(config); + + return config; +} +``` + +### Step 4: Main Index Configuration + +In `src/index.ts`: + +```typescript +import type { ConfigType } from '@plone/registry'; +import installSettings from './config/settings'; + +function applyConfig(config: ConfigType) { + installSettings(config); + return config; +} + +export default applyConfig; +``` + +### Step 5: Import SCSS Files + +In `src/theme/_main.scss`: + +```scss +@import './site'; +``` + +## 2.3 Customizing Block Styles + +Let's add custom styles for specific blocks. In your `_site.scss`, add block-specific styles: + +```scss +:root { + // ... previous variables ... + + #page-document { + .block { + &.__button { + .button { + button { + padding: 1rem; + } + } + } + + &.__button { + .ui.button:hover { + --theme-color: white; + } + } + + &.slider { + .teaser-item-title { + background: rgba(255, 255, 255, 0.1); + color: var(--theme-foreground-color) !important; + backdrop-filter: blur(20px) saturate(110%); + -webkit-backdrop-filter: blur(20px) saturate(180%); + box-shadow: + 0 8px 32px 0 rgba(31, 135, 125, 0.1), + inset 0 0 0 1px rgba(255, 255, 255, 0.1); + } + } + + &.gridBlock { + .four { + .slate { + padding: 2.5rem; + padding-top: 4rem !important; + backdrop-filter: blur(20px) saturate(110%); + -webkit-backdrop-filter: blur(20px) saturate(180%); + box-shadow: + 0 8px 32px 0 rgba(31, 135, 125, 0.1), + inset 0 0 0 1px rgba(255, 255, 255, 0.1); + } + } + } + + &.teaser { + .card { + .card-inner { + .card-summary { + padding: $spacing-large; + } + } + } + } + } + } +} +``` + +--- \ No newline at end of file diff --git a/docs/customizing-volto-light-theme/index.md b/docs/customizing-volto-light-theme/index.md index 74e398160..04260b9a7 100644 --- a/docs/customizing-volto-light-theme/index.md +++ b/docs/customizing-volto-light-theme/index.md @@ -28,9 +28,8 @@ This training is best suited for developers who have prior experience with Volto :numbered: concepts -creating-new-project -installing-vlt -integrate-new-block -theming +design-system-implementation +block-development-widgets +advanced-components-bm3 question-answer ``` \ No newline at end of file diff --git a/docs/customizing-volto-light-theme/installing-vlt.md b/docs/customizing-volto-light-theme/installing-vlt.md deleted file mode 100644 index 32cc59343..000000000 --- a/docs/customizing-volto-light-theme/installing-vlt.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -myst: - html_meta: - "description": "Installing Volto Light Theme" - "property=og:description": "Installing Volto Light Theme" - "property=og:title": "Installing Volto Light Theme" - "keywords": "Plone, Volto, Training, Volto Light Theme" ---- - -# Installing Volto Light Theme - -Follow the steps below to install and configure VLT in your project. VLT provides a clean and modern design with ready-to-use blocks and components. - -## Step 1: Install Volto Light Theme - -To install VLT, navigate to the {file}`frontend/packages/volto-my-project` folder and run the following command: - -```{note} -For now we'll be using an `alpha` release, so we need to specify the correct version. -``` - -```shell -pnpm install @kitconcept/volto-light-theme@6.0.0-alpha.2 -``` - -While in your project package folder, add VLT to the `addons` list in your {file}`package.json`, as follows: - -```json -"addons": ["@kitconcept/volto-light-theme"], -``` - -## Step 2: install block add-ons - -Volto Light Theme comes with several pre-configured add-ons that provide basic blocks for your website. If you'd like to include them, you can add them in the `addons` section in your {file}`package.json`, but this is not required. - -Here is the list of recommended addons to install, including VLT, which should be the last element: - -```json -"addons": [ - "@eeacms/volto-accordion-block", - "@kitconcept/volto-button-block", - "@kitconcept/volto-heading-block", - "@kitconcept/volto-highlight-block", - "@kitconcept/volto-introduction-block", - "@kitconcept/volto-separator-block", - "@kitconcept/volto-slider-block", - "@kitconcept/volto-light-theme" -], -``` - -## Step 3: configure Volto Light Theme as the theme provider - -To leverage a cohesive set of styles, components, and design patterns that align with Volto's best practices, you need to set VLT as your theme provider. - -Open the {file}`volto.config.js` file in your {file}`frontend` folder, and modify it as shown below. - -```{code-block} js -:emphasize-lines: 2 - -const addons = ['volto-project-title']; -const theme = '@kitconcept/volto-light-theme'; - -module.exports = { - addons, - theme -}; -``` - -You'll need to restart your Plone frontend to see the changes. - -That's it! Your project should now be using Volto Light Theme with its additional blocks and components. diff --git a/docs/customizing-volto-light-theme/integrate-new-block.md b/docs/customizing-volto-light-theme/integrate-new-block.md deleted file mode 100644 index 7086e08b4..000000000 --- a/docs/customizing-volto-light-theme/integrate-new-block.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -myst: - html_meta: - "description": "Adding New Block" - "property=og:description": "Adding New Block" - "property=og:title": "Adding New Block" - "keywords": "Plone, Volto, Training, Volto Light Theme, Integrate, block" ---- - -# Integrate a new block - -In this training module, we'll learn how to integrate the `@plone-collective/volto-relateditems-block` into VLT. This block allows you to create links to related content in your Plone site. - -## Install `@plone-collective/volto-relateditems-block` - -To install the related items block, make sure you are in the {file}`frontend/packages/volto-my-project` folder, and use the following command: - -```{note} -For now we'll be using an `alpha` release, so we need to specify the correct version. -``` - -```shell -pnpm install @plone-collective/volto-relateditems-block@1.0.0-alpha.1 -``` - -After installation, ensure that the add-on is included in the `addons` key of your project's {file}`package.json`: - -```json -"addons": [ - "@eeacms/volto-accordion-block", - "@kitconcept/volto-button-block", - "@kitconcept/volto-heading-block", - "@kitconcept/volto-highlight-block", - "@kitconcept/volto-introduction-block", - "@kitconcept/volto-separator-block", - "@kitconcept/volto-slider-block", - "@plone-collective/volto-relateditems-block", - "@kitconcept/volto-light-theme" -], -``` - -You'll need to restart your Plone frontend to see the changes. - -That's it! Your project should now be using `@plone-collective/volto-relateditems-block`, which shows related items for the content as a list of links. diff --git a/docs/customizing-volto-light-theme/theming.md b/docs/customizing-volto-light-theme/theming.md deleted file mode 100644 index 5e3d94699..000000000 --- a/docs/customizing-volto-light-theme/theming.md +++ /dev/null @@ -1,342 +0,0 @@ ---- -myst: - html_meta: - "description": "Extend the Volto Light Theme" - "property=og:description": "Extend the Volto Light Theme" - "property=og:title": "Extend the Volto Light Theme" - "keywords": "Plone, Volto, Training, Extend, Volto Light Theme" ---- - -# Extend VLT - -In this section we'll extend the VLT to create a "dark" aesthetic for our project. The same patterns can be applied for other visual identity cases. - -## File structure - -Let us start by setting up the recommended file structure. In your project add-on's {file}`src` folder, create a subfolder named {file}`theme`. -Inside {file}`theme` create two empty files named {file}`_main.scss` and {file}`_variables.scss`. Refer to the following file system diagram: - -```console -src/ -├── components -├── index.js -└── theme - ├── _main.scss - └── _variables.scss -``` - -Remember that if you add new files to your project, it will be necessary to restart your Plone frontend. - -### `_variables.scss` - -{file}`_variables.scss` is where you can override the base theme SCSS variables. - -```scss -:root { - --primary-color: black; - --primary-foreground-color: lemonchiffon; - - --secondary-color: darkslategrey; - --secondary-foreground-color: lemonchiffon; - - --accent-color: darkslategrey; - --accent-foreground-color: lemonchiffon; - - --link-color: lightblue; -} -``` - -### `_main.scss` - -{file}`_main.scss` is where you should put any custom styles. You can also include other SCSS or CSS files, as follows: - -```scss -@import 'variables'; -``` - -## Block themes - -Now we need to change the available themes for the blocks by adding the following definition inside the `applyConfig` function in our project's {file}`index.js`: - -```js -config.blocks.themes = [ - { - style: { - '--theme-color': 'black', - '--theme-high-contrast-color': 'darkslategrey', - '--theme-foreground-color': 'lemonchiffon', - '--theme-low-contrast-foreground-color': 'lightgrey', - }, - name: 'default', - label: 'Default', - }, - { - style: { - '--theme-color': 'darkslategrey', - '--theme-high-contrast-color': 'black', - '--theme-foreground-color': 'lemonchiffon', - '--theme-low-contrast-foreground-color': 'lightgrey', - }, - name: 'green', - label: 'Green', - }, -]; -``` - -## Extend add-on styles - -The theme provides the ability to extend or modify existing components. Let's create one more file named {file}`relatedItems.scss` to add specific styles for the add-on, as follows: - -```console -src/ -├── components -├── index.js -└── theme - ├── blocks - └── _relatedItems.scss - ├── _main.scss - └── _variables.scss -``` - -In the new {file}`_relatedItems.scss`, let's fix the text color, and add background color to the `.inner-container` element with the variables `--primary-foreground-color` and `--theme-high-contrast-color`. Lastly, let's use `--link-foreground-color` for the related items links: - -```scss -.block.relatedItems { - .inner-container { - background: var(--theme-high-contrast-color); - padding: 3rem; - - h2.headline { - color: var(--primary-foreground-color); - } - - ul.items-list { - color: var(--primary-foreground-color); - li a { - color: var(--link-foreground-color); - } - } - } -} -``` - -And now we need to add {file}`_relatedItems.scss` in {file}`_main_.scss`: - -```scss -@import 'blocks/relatedItems'; -@import 'variables'; -``` - -## Enhancing a block schema - -To be able to use the Block Width widget with our Related Items block, we need to add it to the block schema. -To do this we'll use a `schemaEnhancer`. A schema enhancer is a function that receives an object with `formData` (the block `data`), the `schema` (the original schema that we want to tweak), and the injected `intl` (to aid with internationalization). - -Usually we would want to keep the schema enhancers in individual files per block, after which they can be imported to the {file}`index.js`. For this example we'll leave everything in the same file: - -```js -import { defineMessages } from 'react-intl'; -import { composeSchema } from '@plone/volto/helpers/Extensions'; -import { defaultStylingSchema } from '@kitconcept/volto-light-theme/components/Blocks/schema'; -import { addStyling } from '@plone/volto/helpers/Extensions/withBlockSchemaEnhancer'; - -const messages = defineMessages({ - BlockWidth: { - id: 'Block Width', - defaultMessage: 'Block Width', - }, -}); - -const applyConfig = (config) => { - config.settings = { - ...config.settings, - isMultilingual: false, - supportedLanguages: ['en'], - defaultLanguage: 'en', - }; - - config.blocks.themes = [ - { - style: { - '--theme-color': 'black', - '--theme-high-contrast-color': 'darkslategrey', - '--theme-foreground-color': 'lemonchiffon', - '--theme-low-contrast-foreground-color': 'lightgrey', - }, - name: 'default', - label: 'Default', - }, - { - style: { - '--theme-color': 'darkslategrey', - '--theme-high-contrast-color': 'black', - '--theme-foreground-color': 'lemonchiffon', - '--theme-low-contrast-foreground-color': 'lightgrey', - }, - name: 'green', - label: 'Green', - }, - ]; - - const relatedItemsEnhancer = ({ formData, schema, intl }) => { - addStyling({ schema, intl }); - - schema.properties.styles.schema.fieldsets[0].fields = [ - 'blockWidth:noprefix', - ...schema.properties.styles.schema.fieldsets[0].fields, - ]; - schema.properties.styles.schema.properties['blockWidth:noprefix'] = { - widget: 'blockWidth', - title: intl.formatMessage(messages.BlockWidth), - default: 'default', - filterActions: ['narrow', 'default'], - }; - return schema; - }; - - config.blocks.blocksConfig.relatedItems = { - ...config.blocks.blocksConfig.relatedItems, - schemaEnhancer: composeSchema(defaultStylingSchema, relatedItemsEnhancer), - }; - - return config; -}; - -export default applyConfig; -``` - -Finally, let's wire the width classes for our block in the file {file}`_relatedItems.scss`: - -```scss -.block.relatedItems { - margin-right: auto; - margin-left: auto; - - .inner-container { - background: var(--theme-high-contrast-color); - padding: 3rem; - - h2.headline { - color: var(--primary-foreground-color); - } - - ul { - color: var(--primary-foreground-color); - li a { - color: var(--link-foreground-color); - } - } - } - - &.has--block-width--narrow { - max-width: var(--narrow-container-width) !important; - } - - &.has--block-width--default { - max-width: var(--default-container-width) !important; - } -} - -.block-editor-relatedItems { - &.has--block-width--narrow .block .block .block { - max-width: var(--narrow-container-width) !important; - } - - &.has--block-width--default .block .block .block { - max-width: var(--default-container-width) !important; - } -} -``` - - -## Set themes for one block - -To demonstrate this feature, we'll add a custom list of themes just for the Related Items block, which will include one more theme called `Blue`. Add the property `themes` to the `relatedItems` block in the `blocksConfig` object: - -```js -import { defineMessages } from 'react-intl'; -import { composeSchema } from '@plone/volto/helpers/Extensions'; -import { defaultStylingSchema } from '@kitconcept/volto-light-theme/components/Blocks/schema'; -import { addStyling } from '@plone/volto/helpers/Extensions/withBlockSchemaEnhancer'; - -const messages = defineMessages({ - BlockWidth: { - id: 'Block Width', - defaultMessage: 'Block Width', - }, -}); - -const applyConfig = (config) => { - config.settings = { - ...config.settings, - isMultilingual: false, - supportedLanguages: ['en'], - defaultLanguage: 'en', - }; - - config.blocks.themes = [ - { - style: { - '--theme-color': 'black', - '--theme-high-contrast-color': 'darkslategrey', - '--theme-foreground-color': 'lemonchiffon', - '--theme-low-contrast-foreground-color': 'lightgrey', - }, - name: 'default', - label: 'Default', - }, - { - style: { - '--theme-color': 'darkslategrey', - '--theme-high-contrast-color': 'black', - '--theme-foreground-color': 'lemonchiffon', - '--theme-low-contrast-foreground-color': 'lightgrey', - }, - name: 'green', - label: 'Green', - }, - ]; - - const relatedItemsEnhancer = ({ formData, schema, intl }) => { - addStyling({ schema, intl }); - - schema.properties.styles.schema.fieldsets[0].fields = [ - 'blockWidth:noprefix', - ...schema.properties.styles.schema.fieldsets[0].fields, - ]; - schema.properties.styles.schema.properties['blockWidth:noprefix'] = { - widget: 'blockWidth', - title: intl.formatMessage(messages.BlockWidth), - default: 'default', - filterActions: ['narrow', 'default'], - }; - return schema; - }; - - config.blocks.blocksConfig.relatedItems = { - ...config.blocks.blocksConfig.relatedItems, - schemaEnhancer: composeSchema(defaultStylingSchema, relatedItemsEnhancer), - themes: [ - ...config.blocks.themes, - { - style: { - '--theme-color': 'midnightblue', - '--theme-high-contrast-color': 'black', - '--theme-foreground-color': 'lemonchiffon', - '--theme-low-contrast-foreground-color': 'lightgrey', - }, - name: 'blue', - label: 'Blue', - }, - ], - }; - - return config; -}; - -export default applyConfig; -``` - -## Conclusion - -Understanding how to extend VLT will help you take advantage of the system and quickly create consistent and flexible designs. From f4eba8e5419f6ee1492110197934ed8c66730c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dante=20=C3=81lvarez?= <89805481+danalvrz@users.noreply.github.com> Date: Sat, 11 Oct 2025 01:37:33 -0600 Subject: [PATCH 02/10] improve training update --- .../advanced-components-bm3.md | 181 ++++++++++--- .../block-development-widgets.md | 108 +++----- .../customizing-volto-light-theme/concepts.md | 10 +- .../design-system-implementation.md | 246 +++--------------- 4 files changed, 209 insertions(+), 336 deletions(-) diff --git a/docs/customizing-volto-light-theme/advanced-components-bm3.md b/docs/customizing-volto-light-theme/advanced-components-bm3.md index b90172a9d..221f8f915 100644 --- a/docs/customizing-volto-light-theme/advanced-components-bm3.md +++ b/docs/customizing-volto-light-theme/advanced-components-bm3.md @@ -7,9 +7,9 @@ myst: "keywords": "Plone, Volto, Training, Volto Light Theme, Integrate, block" --- -# 4. Advanced Components, Site Customization & Block Model v3 +# Advanced Components, Site Customization & Block Model v3 -## 4.1 Understanding the Card Primitive +## Understanding the Card Primitive The Card primitive is VLT's reusable component for displaying content in card layouts. It has three configurable slots: image, summary, and actions. @@ -69,7 +69,7 @@ const Summary = config.getComponent({ ``` -## 4.2 Creating Custom Summary Components +## Creating Custom Summary Components The Summary component displays content metadata in listings, teasers, and cards. VLT includes built-in implementations: @@ -97,13 +97,19 @@ First, create a custom "Product" content type through the Plone UI: ### Step 2: Create a Custom Summary Component -Create `src/components/Summary/ProductSummary.jsx`: +Create `src/components/Summary/ProductSummary.tsx`: -```jsx +```tsx import { FormattedNumber } from 'react-intl'; -const ProductSummary = ({ item, HeadingTag = 'h3' }) => { - const { title, description, head_title, currency = 'EUR' } = item; +const ProductSummary = ({ item }) => { + const { + title, + description, + HeadingTag = 'h3', + head_title, + currency = 'EUR', + } = item; const price = parseFloat(head_title); return ( @@ -142,9 +148,7 @@ Create `src/theme/_productSummary.scss`: padding-bottom: 0 !important; .product-price { font-size: 1.25rem; - font-weight: var(--font-bold); color: var(--accent-color); - margin-bottom: 0.5rem; margin-top: 0.5rem; .price { @@ -196,7 +200,7 @@ export default function install(config: ConfigType) { 3. Add the Product to a Listing or Teaser block 4. The custom ProductSummary will display the price, title, and description -## 4.3 Creating Custom Listing Variations with Card Actions +## Creating Custom Listing Variations with Card Actions Listing variations customize content display in Listing blocks. Let's create a ProductTemplate demonstrating the Card.Actions slot for interactive buttons. @@ -209,9 +213,9 @@ The Card.Actions slot provides interactive elements beyond the main card link: ### Step 1: Create ProductActions Component -Create `src/components/Actions/ProductAction.jsx`: +Create `src/components/Actions/ProductActions.tsx`: -```jsx +```tsx const ProductActions = ({ item }) => { return ( <> @@ -228,7 +232,7 @@ export default ProductActions; Update `src/config/settings.ts`: ```typescript -import ProductActions from '../components/Actions/ProductAction'; +import ProductActions from '../components/Actions/ProductActions'; export default function install(config: ConfigType) { // ... previous configuration ... @@ -246,9 +250,9 @@ export default function install(config: ConfigType) { ### Step 3: Create ProductTemplate Listing Variation -Create `src/components/blocks/Listing/ProductTemplate.jsx`: +Create `src/components/blocks/Listing/ProductTemplate.tsx`: -```jsx +```tsx import React from 'react'; import PropTypes from 'prop-types'; import ConditionalLink from '@plone/volto/components/manage/ConditionalLink/ConditionalLink'; @@ -369,24 +373,126 @@ Create `src/theme/blocks/_listing.scss`: ```scss .block.listing { &.products { + max-width: var(--default-container-width) !important; + &.next--has--same--backgroundColor.next--is--same--block-type, + &.next--is--__button { + .listing-item:last-child { + padding-bottom: 0 !important; + border-bottom: none !important; + } + } + .items { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 2rem; + display: flex; + flex-wrap: wrap; + @media only screen and (max-width: $largest-mobile-screen) { + flex-direction: column; + + .listing-item { + padding-bottom: 20px !important; + } + } + } + .headline { + @include block-title(); + margin-right: 0 !important; + margin-left: 0 !important; + } + .listing-item { + align-items: normal; + border-bottom: none; + margin: 0 !important; + + @media only screen and (min-width: $tablet-breakpoint) { + width: 50%; + padding-top: 10px; + padding-bottom: 10px !important; + + &:nth-child(2n) { + padding-left: 10px !important; + } + + &:nth-child(2n + 1) { + padding-right: 10px !important; + } + + &:last-child, + &:nth-last-child(2):not(:nth-child(2n)) { + padding-bottom: 0 !important; + } + + &:first-child, + &:nth-child(2) { + padding-top: 0 !important; + } + } + + &:last-child:nth-child(2n + 1) { + @media only screen and (min-width: $largest-mobile-screen) { + margin-left: 0 !important; + } + } .card { - .card-actions { - padding: 1rem; - border-top: 1px solid var(--theme-high-contrast-color); + flex-grow: 1; + .card-inner { + background: var(--theme-high-contrast-color); + .image-wrapper { + img { + width: 100%; + margin: 0; + aspect-ratio: var(--image-aspect-ratio, $aspect-ratio) !important; + } + } + .card-summary { + padding: 0 $spacing-large $spacing-large $spacing-large; + margin-top: $spacing-medium; + + .headline { + padding: 0 !important; + margin-bottom: $spacing-small; + letter-spacing: 1px; + text-transform: uppercase; + @include headtitle1(); + @include word-break(); + } + + h2 { + margin: 0 0 $spacing-small 0; + @include text-heading-h3(); + } + p { + margin-bottom: 0; + @include body-text(); + } + p:empty { + display: none; + } + } + + .product-price { + display: flex; + justify-content: end; + padding: $spacing-small; - .add-to-cart { - width: 100%; + .price { + font-size: 2.5rem; + width: 30%; + } + } + } + + .actions-wrapper { + display: flex; + justify-content: end; + padding: 0 $spacing-large $spacing-medium $spacing-large; + .rent-now-button { + width: 30%; padding: 0.75rem 1rem; + margin: 1rem; background-color: var(--accent-color); - color: var(--accent-foreground-color); + color: white; border: none; - border-radius: 4px; - font-weight: var(--font-bold); cursor: pointer; transition: opacity 0.2s; @@ -419,7 +525,7 @@ Import in `src/theme/_main.scss`: 4. Configure to show Product content type 5. Products display with image, summary, and "Get now" button -## 4.4 Working with Slots +## Working with Slots VLT provides slots for extending the layout without component shadowing. Let's create a practical example: a newsletter signup component in the preFooter slot. @@ -436,9 +542,9 @@ VLT provides slots for extending the layout without component shadowing. Let's c ### Step 1: Create Newsletter Signup Component -Create `src/components/NewsletterSignup/NewsletterSignup.jsx`: +Create `src/components/NewsletterSignup/NewsletterSignup.tsx`: -```jsx +```tsx import React, { useState } from 'react'; const NewsletterSignup = () => { @@ -499,7 +605,6 @@ Create `src/theme/_newsletterSignup.scss`: .newsletter-title { font-size: 2rem; margin-bottom: 1rem; - font-weight: var(--font-bold); } .newsletter-description { @@ -537,7 +642,6 @@ Create `src/theme/_newsletterSignup.scss`: color: var(--accent-foreground-color); border: none; font-size: 1rem; - font-weight: var(--font-bold); cursor: pointer; transition: opacity 0.2s; @@ -587,7 +691,7 @@ export default function install(config: ConfigType) { The newsletter signup component will now appear before the footer on all pages, demonstrating how slots enable you to extend the layout without shadowing core components. -## 4.5 Site Customization Behaviors +## Site Customization Behaviors VLT provides backend behaviors for site customization that you activated earlier. These behaviors enable fields for customizing the site without code changes. @@ -614,7 +718,7 @@ Through the Plone UI, you can customize: - **Footer logos**: List of logos with links and customizable size (small/large) and container width (default/layout) - **Footer colophon text**: Customizable last line of footer -## 4.6 Block Model v3 (opt-in) +## Block Model v3 (opt-in) ```{note} Block Model v3 is a beta feature. It's recommended to only use it when all blocks in your registry are v3-compatible (indicated by banner in block's GitHub repository). @@ -670,14 +774,15 @@ In your project's `src/config/settings.ts`: ```typescript export default function install(config: ConfigType) { - // Enable Block Model v3 globally + // Enable Block Model v3 config.settings.blockModel = 3; - + config.blocks.blocksConfig.slate.blockModel = config.settings.blockModel; + config.blocks.blocksConfig.title.blockModel = config.settings.blockModel; + config.blocks.blocksConfig.__button.blockModel = config.settings.blockModel; // Define block categories for spacing config.blocks.blocksConfig.slate.category = 'inline'; config.blocks.blocksConfig.title.category = 'title'; config.blocks.blocksConfig.__button.category = 'action'; - config.blocks.blocksConfig.gridBlock.category = 'cards'; return config; } @@ -694,7 +799,7 @@ Vertical spacing between blocks is provided by the **upper block**: ### View Mode Structure -```jsx +```tsx
{ - const { data, className, style } = props; - const { title, subtitle, backgroundImage } = data; +const HeroView = (props: BlockViewProps) => { + const { className, style } = props; + const { title, subtitle, backgroundImage, image_scales, url } = props?.data; let renderedImage = null; - if (data.backgroundImage) { + if (backgroundImage) { let Image = config.getComponent('Image').component; if (Image) { renderedImage = ( { renderedImage = ( {
- {title &&

{title}

} - {subtitle &&

{subtitle}

} + {title &&

{title as any}

} + {subtitle &&

{subtitle as any}

}
{backgroundImage && ( @@ -198,7 +198,7 @@ const HeroView = (props) => { ); }; -export default withBlockExtensions(HeroView); +export default HeroView; ``` ### Step 3: Create Edit Component @@ -252,7 +252,7 @@ Update `src/config/blocks.ts`: import type { ConfigType } from '@plone/registry'; import HeroView from '../components/blocks/myHero/View'; import HeroEdit from '../components/blocks/myHero/Edit'; -import { heroBlockSchema } from '../components/blocks/myHero/schema.js'; +import { heroBlockSchema } from '../components/blocks/myHero/schema'; import heroSVG from '@plone/volto/icons/hero.svg'; export default function install(config: ConfigType) { @@ -309,14 +309,12 @@ Create `src/theme/blocks/_hero.scss`: .hero-title { font-size: 5rem; - font-weight: var(--font-bold); - margin-bottom: var(--space-3); + margin-bottom: $spacing-small; line-height: 1; } .hero-subtitle { font-size: 3rem; - margin-bottom: var(--space-6); opacity: 0.95; line-height: 1; } @@ -443,7 +441,7 @@ import HeroEdit from '../components/blocks/myHero/Edit'; import { heroBlockSchema, heroSchemaEnhancer, -} from '../components/blocks/myHero/schema.js'; +} from '../components/blocks/myHero/schema'; import { composeSchema } from '@plone/volto/helpers/Extensions'; import { defaultStylingSchema } from '@kitconcept/volto-light-theme/components/Blocks/schema'; import heroSVG from '@plone/volto/icons/hero.svg'; @@ -509,14 +507,12 @@ Update `src/theme/blocks/_hero.scss` to use the CSS custom properties set by the .hero-title { font-size: 5rem; - font-weight: var(--font-bold); - margin-bottom: var(--space-3); + margin-bottom: $spacing-small; line-height: 1; } .hero-subtitle { font-size: 3rem; - margin-bottom: var(--space-6); opacity: 0.95; line-height: 1; } @@ -525,12 +521,14 @@ Update `src/theme/blocks/_hero.scss` to use the CSS custom properties set by the } ``` -## 3.3 Integrating Third-Party Blocks +## Integrating Third-Party Blocks Let's learn how to integrate the `@plone-collective/volto-relateditems-block` into VLT. ### Install the Block +To install the related items block, make sure you are in the `frontend/packages/volto-my-project` folder, and use the following command: + ```bash pnpm install @plone-collective/volto-relateditems-block@latest ``` @@ -553,52 +551,15 @@ Add it to your `package.json` addons (before VLT): ### Enhance the Block Schema -To add the Block Width widget to the Related Items block, create a schema enhancer file. - -Create `src/components/blocks/relatedItems/schemaEnhancer.js`: - -```javascript -import { defineMessages } from 'react-intl'; - -const messages = defineMessages({ - blockWidth: { - id: 'Block Width', - defaultMessage: 'Block Width', - }, -}); - -const relatedItemsSchemaEnhancer = ({ formData, schema, intl }) => { - schema.properties.styles.schema.fieldsets[0].fields = [ - 'blockWidth:noprefix', - ...schema.properties.styles.schema.fieldsets[0].fields, - ]; - - schema.properties.styles.schema.properties['blockWidth:noprefix'] = { - widget: 'blockWidth', - title: intl.formatMessage(messages.blockWidth), - default: 'default', - filterActions: ['narrow', 'default'], - }; - - return schema; -}; - -export default relatedItemsSchemaEnhancer; -``` - -Then update `src/config/blocks.ts` to register the enhancer: +To add the theme feature to the Related Items block, update `src/config/blocks.ts` to register the `defaultStylingSchema` enhancer: ```typescript -import { composeSchema } from '@plone/volto/helpers/Extensions'; -import { defaultStylingSchema } from '@kitconcept/volto-light-theme/components/Blocks/schema'; -import relatedItemsSchemaEnhancer from '../components/blocks/relatedItems/schemaEnhancer'; - export default function install(config: ConfigType) { // ... previous configuration ... config.blocks.blocksConfig.relatedItems = { ...config.blocks.blocksConfig.relatedItems, - schemaEnhancer: composeSchema(defaultStylingSchema, relatedItemsSchemaEnhancer), + schemaEnhancer: defaultStylingSchema, }; return config; @@ -611,13 +572,10 @@ Create `src/theme/blocks/_relatedItems.scss`: ```scss .block.relatedItems { - max-width: var(--block-width); - margin-right: auto; - margin-left: auto; - .inner-container { background: var(--theme-high-contrast-color); padding: 3rem; + width: var(--narrow-container-width); h2.headline { color: var(--theme-foreground-color); @@ -630,8 +588,6 @@ Create `src/theme/blocks/_relatedItems.scss`: } } } - - } ``` diff --git a/docs/customizing-volto-light-theme/concepts.md b/docs/customizing-volto-light-theme/concepts.md index 70ef7e4cd..f324158d9 100644 --- a/docs/customizing-volto-light-theme/concepts.md +++ b/docs/customizing-volto-light-theme/concepts.md @@ -6,9 +6,9 @@ myst: "property=og:title": "Foundation, Concepts & Project Setup" "keywords": "Plone, Volto, Training, Volto Light Theme" --- -# 1. Foundation, Concepts & Project Setup +# Foundation, Concepts & Project Setup -## 1.1 Volto Light Theme Core Concepts +## Volto Light Theme Core Concepts Volto Light Theme (VLT) is a customizable theme built for the Volto frontend of the Plone CMS. It provides a foundation that aims to solve many common design challenges, while remaining flexible enough for customization. It's particularly valuable because it is based on real-world experience, while simultaneously embodying the Volto vision for the future. This module will help you understand the core concepts in VLT and create a mental map of its parts. @@ -139,11 +139,11 @@ The `BlockAlignmentWidget` takes advantage of the StyleWrapper by setting the `- --align-right: end; ``` -## 1.2 Create a New Project with Cookieplone +## Create a New Project with Cookieplone We recommend creating your Plone project with **Cookieplone**. Our comprehensive documentation provides step-by-step guidance to help you get started. For detailed installation instructions, visit our [Cookieplone guide](https://6.docs.plone.org/install/create-project-cookieplone.html). -## 1.3 Installing Volto Light Theme +## Installing Volto Light Theme ### Step 1: Install VLT and Recommended Block Add-ons @@ -252,7 +252,7 @@ To enable site customization through the UI: Now your project should have the VLT Site configurations available. -## 1.4 File Structure Setup +## File Structure Setup Let's set up the recommended file structure. In your project add-on's `src` folder, create the following structure: diff --git a/docs/customizing-volto-light-theme/design-system-implementation.md b/docs/customizing-volto-light-theme/design-system-implementation.md index 71cabcf3e..eaadd9aa3 100644 --- a/docs/customizing-volto-light-theme/design-system-implementation.md +++ b/docs/customizing-volto-light-theme/design-system-implementation.md @@ -7,9 +7,9 @@ myst: "keywords": "Plone, Volto, Training, Theme, Footer" --- -# 2. Design System Implementation & Theming +# Design System Implementation & Theming -## 2.1 Extracting Design Tokens +## Extracting Design Tokens When working with a given design, systematically extract design decisions. Identify: @@ -38,13 +38,26 @@ Look for: - Font weights -## 2.2 Implementing Your Design System +## Implementing Your Design System VLT has migrated to use standardized color definitions. These use CSS properties that are injected at runtime in the right places, so your CSS can adapt to use them generically. The resulting CSS is simpler, and there's no need to define class names for each color definition. -### Step 1: Create _site.scss +### Step 1: Add Font Files -In `src/theme/_site.scss`, define your color variables and custom properties: +If you're using custom fonts, add the font files to your theme directory. Create a `fonts` folder in your theme directory and place your font files there: + +``` +src/theme/fonts/Chakra_Petch/ + ├── ChakraPetch-Regular.ttf + ├── ChakraPetch-Bold.ttf + └── ... (other font weights/styles) +``` + +This ensures the font files are bundled with your theme and can be referenced in your SCSS files. + +### Step 2: Create _site.scss + +In `src/theme/_site.scss`, define your color variables, custom properties and block-specific styles: ```scss @font-face { @@ -181,7 +194,7 @@ In `src/theme/_site.scss`, define your color variables and custom properties: } ``` -### Step 2: Configure Block Themes +### Step 3: Configure Block Themes In `src/config/blocks.ts`, define block themes: @@ -203,133 +216,14 @@ export default function install(config: ConfigType) { }, { style: { - '--theme-color': `/* White edge bleed for continuity */ - linear-gradient(180deg, - rgba(255, 255, 255, 0.9) 0%, - rgba(255, 255, 255, 0.6) 8%, - rgba(255, 255, 255, 0.3) 12%, - transparent 18%, - transparent 82%, - rgba(255, 255, 255, 0.3) 88%, - rgba(255, 255, 255, 0.6) 92%, - rgba(255, 255, 255, 0.9) 100%), - - /* Top white highlights - asymmetric cluster */ - radial-gradient(ellipse 900px 650px at 12% 8%, - rgba(255, 255, 255, 0.38) 0%, - rgba(243, 251, 251, 0.26) 28%, - rgba(228, 244, 244, 0.14) 52%, - transparent 72%), - - radial-gradient(ellipse 750px 850px at 6% 22%, - rgba(255, 255, 255, 0.34) 0%, - rgba(238, 249, 249, 0.2) 32%, - rgba(220, 240, 240, 0.1) 58%, - transparent 78%), - - radial-gradient(ellipse 820px 580px at 88% 12%, - rgba(255, 255, 255, 0.42) 0%, - rgba(246, 252, 252, 0.28) 30%, - rgba(232, 246, 246, 0.15) 54%, - transparent 74%), - - /* Bottom white highlights - dispersed asymmetrically */ - radial-gradient(ellipse 980px 620px at 82% 88%, - rgba(255, 255, 255, 0.4) 0%, - rgba(244, 251, 251, 0.27) 26%, - rgba(230, 245, 245, 0.13) 50%, - transparent 73%), - - radial-gradient(ellipse 680px 780px at 18% 90%, - rgba(255, 255, 255, 0.35) 0%, - rgba(240, 250, 250, 0.22) 34%, - rgba(225, 242, 242, 0.11) 60%, - transparent 80%), - - radial-gradient(ellipse 720px 520px at 68% 94%, - rgba(255, 255, 255, 0.3) 0%, - rgba(235, 247, 247, 0.17) 38%, - transparent 68%), - - /* Color hotspot 1 - upper right quadrant */ - radial-gradient(ellipse 850px 750px at 70% 35%, - rgba(175, 202, 200, 0.5) 0%, - rgba(180, 206, 204, 0.38) 25%, - rgba(190, 212, 210, 0.24) 45%, - rgba(200, 220, 218, 0.12) 65%, - transparent 85%), - - /* Color hotspot 2 - lower left quadrant */ - radial-gradient(ellipse 920px 800px at 25% 65%, - rgba(165, 195, 193, 0.45) 0%, - rgba(175, 202, 200, 0.34) 28%, - rgba(185, 210, 208, 0.22) 50%, - rgba(195, 215, 213, 0.11) 70%, - transparent 87%), - - /* Color hotspot 3 - center right */ - radial-gradient(circle 700px at 78% 52%, - rgba(170, 198, 196, 0.42) 0%, - rgba(180, 205, 203, 0.3) 30%, - rgba(192, 214, 212, 0.18) 55%, - rgba(205, 222, 220, 0.09) 75%, - transparent 90%), - - /* Organic flow connecting hotspots */ - radial-gradient(ellipse 1100px 900px at 45% 48%, - rgba(175, 202, 200, 0.28) 0%, - rgba(185, 210, 208, 0.18) 35%, - rgba(195, 216, 214, 0.1) 60%, - rgba(205, 222, 220, 0.05) 78%, - transparent 90%), - - /* Additional scattered color pockets */ - radial-gradient(circle 550px at 15% 40%, - rgba(172, 198, 196, 0.32) 0%, - rgba(182, 207, 205, 0.2) 38%, - rgba(195, 216, 214, 0.1) 68%, - transparent 85%), - - radial-gradient(ellipse 680px 580px at 88% 68%, - rgba(178, 203, 201, 0.35) 0%, - rgba(188, 210, 208, 0.22) 35%, - rgba(200, 218, 216, 0.11) 65%, - transparent 82%), - - radial-gradient(ellipse 620px 720px at 52% 28%, - rgba(168, 196, 194, 0.3) 0%, - rgba(180, 205, 203, 0.18) 40%, - rgba(195, 215, 213, 0.08) 70%, - transparent 88%), - - radial-gradient(circle 480px at 35% 78%, - rgba(175, 200, 198, 0.28) 0%, - rgba(188, 210, 208, 0.15) 42%, - transparent 75%), - - /* Asymmetrical accent flows */ - conic-gradient(from 125deg at 28% 44%, - transparent 0deg, - rgba(178, 203, 201, 0.18) 48deg, - rgba(185, 210, 208, 0.14) 95deg, - transparent 145deg, - rgba(172, 198, 196, 0.12) 235deg, - transparent 285deg), - - /* Base gradient foundation - subtle */ - linear-gradient(182deg, - #ffffff 0%, - #f9fcfc 10%, - #ebf4f3 22%, - #d8e6e5 35%, - #c5d8d6 45%, - #b8cece 52%, - #c5d8d6 59%, - #d8e6e5 69%, - #ebf4f3 82%, - #f9fcfc 92%, - #ffffff 100%)`, - '--theme-high-contrast-color': 'rgba(255, 255, 255, 0.1)', + '--theme-color': `linear-gradient(180deg, oklab(1 0 0 / 0.9) 0%, transparent 15%, transparent 85%, oklab(1 0 0 / 0.9) 100%), + radial-gradient(ellipse 850px 700px at 12% 15%, oklab(1 0 0 / 0.4) 0%, transparent 70%), + radial-gradient(ellipse 900px 650px at 85% 85%, oklab(1 0 0 / 0.4) 0%, transparent 70%), + radial-gradient(ellipse 850px 750px at 70% 35%, oklab(0.805 -0.030 -0.003 / 0.5) 0%, oklab(0.835 -0.025 -0.002 / 0.2) 50%, transparent 85%), + radial-gradient(ellipse 920px 800px at 25% 65%, oklab(0.780 -0.032 -0.004 / 0.45) 0%, oklab(0.825 -0.027 -0.003 / 0.2) 50%, transparent 87%), + radial-gradient(ellipse 1100px 900px at 50% 50%, oklab(0.805 -0.030 -0.003 / 0.25) 0%, oklab(0.850 -0.025 -0.002 / 0.1) 60%, transparent 90%), + linear-gradient(182deg, oklab(1 0 0) 0%, oklab(0.988 -0.006 0) 10%, oklab(0.958 -0.016 -0.001) 22%, oklab(0.910 -0.025 -0.003) 35%, oklab(0.860 -0.030 -0.003) 45%, oklab(0.820 -0.032 -0.004) 52%, oklab(0.860 -0.030 -0.003) 59%, oklab(0.910 -0.025 -0.003) 69%, oklab(0.958 -0.016 -0.001) 82%, oklab(0.988 -0.006 0) 92%, oklab(1 0 0) 100%)`, + '--theme-high-contrast-color': 'oklab(1 0 0 / 0.1)', '--theme-foreground-color': 'black', '--theme-low-contrast-foreground-color': '#555555', }, @@ -342,26 +236,6 @@ export default function install(config: ConfigType) { } ``` -### Step 3: Configure Settings - -In `src/config/settings.ts`: - -```typescript -import type { ConfigType } from '@plone/registry'; -import installBlocks from './blocks'; - -export default function install(config: ConfigType) { - // Language settings - config.settings.isMultilingual = false; - config.settings.supportedLanguages = ['en']; - config.settings.defaultLanguage = 'en'; - - installBlocks(config); - - return config; -} -``` - ### Step 4: Main Index Configuration In `src/index.ts`: @@ -369,9 +243,11 @@ In `src/index.ts`: ```typescript import type { ConfigType } from '@plone/registry'; import installSettings from './config/settings'; +import installBlocks from './config/blocks'; function applyConfig(config: ConfigType) { installSettings(config); + installBlocks(config); return config; } @@ -386,68 +262,4 @@ In `src/theme/_main.scss`: @import './site'; ``` -## 2.3 Customizing Block Styles - -Let's add custom styles for specific blocks. In your `_site.scss`, add block-specific styles: - -```scss -:root { - // ... previous variables ... - - #page-document { - .block { - &.__button { - .button { - button { - padding: 1rem; - } - } - } - - &.__button { - .ui.button:hover { - --theme-color: white; - } - } - - &.slider { - .teaser-item-title { - background: rgba(255, 255, 255, 0.1); - color: var(--theme-foreground-color) !important; - backdrop-filter: blur(20px) saturate(110%); - -webkit-backdrop-filter: blur(20px) saturate(180%); - box-shadow: - 0 8px 32px 0 rgba(31, 135, 125, 0.1), - inset 0 0 0 1px rgba(255, 255, 255, 0.1); - } - } - - &.gridBlock { - .four { - .slate { - padding: 2.5rem; - padding-top: 4rem !important; - backdrop-filter: blur(20px) saturate(110%); - -webkit-backdrop-filter: blur(20px) saturate(180%); - box-shadow: - 0 8px 32px 0 rgba(31, 135, 125, 0.1), - inset 0 0 0 1px rgba(255, 255, 255, 0.1); - } - } - } - - &.teaser { - .card { - .card-inner { - .card-summary { - padding: $spacing-large; - } - } - } - } - } - } -} -``` - --- \ No newline at end of file From 451922af406cb04c0eec97218d6b8269c0dda358 Mon Sep 17 00:00:00 2001 From: iRohitSingh Date: Sun, 12 Oct 2025 21:33:00 +0300 Subject: [PATCH 03/10] Fix code and css --- .../block-development-widgets.md | 124 +++++++++++------- .../customizing-volto-light-theme/concepts.md | 2 +- 2 files changed, 79 insertions(+), 47 deletions(-) diff --git a/docs/customizing-volto-light-theme/block-development-widgets.md b/docs/customizing-volto-light-theme/block-development-widgets.md index d1f010bd5..30b21f7c3 100644 --- a/docs/customizing-volto-light-theme/block-development-widgets.md +++ b/docs/customizing-volto-light-theme/block-development-widgets.md @@ -144,36 +144,35 @@ import type { BlockViewProps } from '@plone/types'; const HeroView = (props: BlockViewProps) => { const { className, style } = props; - const { title, subtitle, backgroundImage, image_scales, url } = props?.data; + const { title, subtitle, backgroundImage, url } = props?.data || {}; + + const hasImage = backgroundImage?.[0]?.['@id']; let renderedImage = null; - if (backgroundImage) { - let Image = config.getComponent('Image').component; + if (hasImage) { + const Image = config.getComponent('Image').component; + const imageItem = backgroundImage[0]; + if (Image) { renderedImage = ( ); } else { renderedImage = ( { } return ( -
+
+ {hasImage &&
{renderedImage}
} +
- {title &&

{title as any}

} - {subtitle &&

{subtitle as any}

} + {title &&

{title}

} + {subtitle &&

{subtitle}

}
- - {backgroundImage && ( -
{renderedImage}
- )}
); @@ -285,40 +285,72 @@ Create `src/theme/blocks/_hero.scss`: .block.hero { position: relative; display: flex; + width: var(--default-container-width); + max-width: var(--block-width) !important; align-items: center; justify-content: center; - background-size: cover; - background-position: center; + background-color: var(--theme-color); + color: var(--theme-foreground-color); + margin-inline: auto; + // default text view .hero-content { - .hero-image-wrapper { - img { - aspect-ratio: var(--image-aspect-ratio, 16/9); - opacity: 0.8; - } + position: relative; + z-index: 1; + width: 100%; + } + + .hero-text { + display: flex; + width: 100%; + height: 100%; + flex-direction: column; + align-items: var(--align--block-alignment); + gap: 1rem; + text-align: var(--align--block-alignment); + + .hero-title { + margin-bottom: $spacing-small; + font-size: 5rem; + line-height: 1.1; } - .hero-text { + .hero-subtitle { + margin-bottom: $spacing-small; + font-size: 2rem; + line-height: 1.3; + opacity: 0.9; + } + } + + // has-image version + &.has-image { + color: #fff; + + .hero-image-wrapper { position: absolute; - display: flex; - flex-direction: column; - width: 100%; - height: 100%; - padding: 4rem; - z-index: 2; + z-index: 0; + inset: 0; - .hero-title { - font-size: 5rem; - margin-bottom: $spacing-small; - line-height: 1; + img { + width: 100%; + height: 100%; + object-fit: cover; + opacity: 0.7; } - .hero-subtitle { - font-size: 3rem; - opacity: 0.95; - line-height: 1; + &::after { + position: absolute; + background: rgba(0, 0, 0, 0.4); + content: ''; + inset: 0; } } + + .hero-content { + position: relative; + z-index: 1; + } } } ``` @@ -334,7 +366,7 @@ Import in `src/theme/_main.scss`: Now let's add VLT's powerful widgets to control block width and alignment. Update the schema file to add the schema enhancer. -Update `src/components/blocks/myHero/schema.js`: +Update `src/components/blocks/myHero/schema.ts`: ```javascript import { defineMessages } from 'react-intl'; diff --git a/docs/customizing-volto-light-theme/concepts.md b/docs/customizing-volto-light-theme/concepts.md index f324158d9..a5d16434f 100644 --- a/docs/customizing-volto-light-theme/concepts.md +++ b/docs/customizing-volto-light-theme/concepts.md @@ -220,7 +220,7 @@ dependencies = [ "plone.api", "plone.restapi", "plone.volto", - "kitconcept.voltolighttheme==7.3.0", + "kitconcept.voltolighttheme==7.3.1", ] ``` From a9f3ef7bfe3a9e029ba8680112bf7052b49d5220 Mon Sep 17 00:00:00 2001 From: iRohitSingh Date: Mon, 13 Oct 2025 09:51:11 +0300 Subject: [PATCH 04/10] minor fix --- docs/customizing-volto-light-theme/block-development-widgets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/customizing-volto-light-theme/block-development-widgets.md b/docs/customizing-volto-light-theme/block-development-widgets.md index 30b21f7c3..16209a8c4 100644 --- a/docs/customizing-volto-light-theme/block-development-widgets.md +++ b/docs/customizing-volto-light-theme/block-development-widgets.md @@ -285,7 +285,7 @@ Create `src/theme/blocks/_hero.scss`: .block.hero { position: relative; display: flex; - width: var(--default-container-width); + width: 100%; max-width: var(--block-width) !important; align-items: center; justify-content: center; From b058e481caa5c8c3c9c8056753f197e67c55e8b3 Mon Sep 17 00:00:00 2001 From: iRohitSingh Date: Mon, 13 Oct 2025 22:18:24 +0300 Subject: [PATCH 05/10] fix the highlighting issue for sass and less and other build issue --- docs/conf.py | 2 ++ .../advanced-components-bm3.md | 4 +--- .../block-development-widgets.md | 6 +----- docs/customizing-volto-light-theme/concepts.md | 5 +---- .../design-system-implementation.md | 4 +--- docs/mastering-plone/events.md | 2 +- 6 files changed, 7 insertions(+), 16 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index b97b86106..741c82bd6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -6,6 +6,8 @@ from datetime import datetime +suppress_warnings = ["misc.highlighting_failure"] + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. diff --git a/docs/customizing-volto-light-theme/advanced-components-bm3.md b/docs/customizing-volto-light-theme/advanced-components-bm3.md index 221f8f915..c463e6936 100644 --- a/docs/customizing-volto-light-theme/advanced-components-bm3.md +++ b/docs/customizing-volto-light-theme/advanced-components-bm3.md @@ -826,6 +826,4 @@ Vertical spacing between blocks is provided by the **upper block**:
``` -Notice how the actual block content remains identical in both modes, while the framework containers handle all the differences in layout and editing functionality. - ---- +Notice how the actual block content remains identical in both modes, while the framework containers handle all the differences in layout and editing functionality. \ No newline at end of file diff --git a/docs/customizing-volto-light-theme/block-development-widgets.md b/docs/customizing-volto-light-theme/block-development-widgets.md index 16209a8c4..cc5016326 100644 --- a/docs/customizing-volto-light-theme/block-development-widgets.md +++ b/docs/customizing-volto-light-theme/block-development-widgets.md @@ -293,7 +293,6 @@ Create `src/theme/blocks/_hero.scss`: color: var(--theme-foreground-color); margin-inline: auto; - // default text view .hero-content { position: relative; z-index: 1; @@ -323,7 +322,6 @@ Create `src/theme/blocks/_hero.scss`: } } - // has-image version &.has-image { color: #fff; @@ -630,6 +628,4 @@ Import it in `_main.scss`: @import './blocks/hero'; @import './blocks/relatedItems'; @import './site'; -``` - ---- +``` \ No newline at end of file diff --git a/docs/customizing-volto-light-theme/concepts.md b/docs/customizing-volto-light-theme/concepts.md index a5d16434f..993331413 100644 --- a/docs/customizing-volto-light-theme/concepts.md +++ b/docs/customizing-volto-light-theme/concepts.md @@ -280,7 +280,4 @@ touch index.ts touch theme/_main.scss theme/_site.scss ``` -Remember that if you add new files to your project, it will be necessary to restart your Plone frontend. - - ---- \ No newline at end of file +Remember that if you add new files to your project, it will be necessary to restart your Plone frontend. \ No newline at end of file diff --git a/docs/customizing-volto-light-theme/design-system-implementation.md b/docs/customizing-volto-light-theme/design-system-implementation.md index eaadd9aa3..ff014bf02 100644 --- a/docs/customizing-volto-light-theme/design-system-implementation.md +++ b/docs/customizing-volto-light-theme/design-system-implementation.md @@ -260,6 +260,4 @@ In `src/theme/_main.scss`: ```scss @import './site'; -``` - ---- \ No newline at end of file +``` \ No newline at end of file diff --git a/docs/mastering-plone/events.md b/docs/mastering-plone/events.md index bcc7e0ddc..fd6b1b707 100644 --- a/docs/mastering-plone/events.md +++ b/docs/mastering-plone/events.md @@ -267,7 +267,7 @@ This trick does not yet work in Volto because some css-classes are still missing Modify {file}`frontend/theme/extras/custom.overrides` and add: -```less +```css /* Hide date fields from contributors */ body.userrole-contributor { #default-start.field, From 85a5da018ac8c9ac3f0175fd9162e819bdbf02a6 Mon Sep 17 00:00:00 2001 From: Tishasoumya-02 Date: Sat, 29 Aug 2026 16:33:50 -0500 Subject: [PATCH 06/10] update installation guide --- .../customizing-volto-light-theme/concepts.md | 149 +++++++++++++----- 1 file changed, 109 insertions(+), 40 deletions(-) diff --git a/docs/customizing-volto-light-theme/concepts.md b/docs/customizing-volto-light-theme/concepts.md index 993331413..1a3aa1049 100644 --- a/docs/customizing-volto-light-theme/concepts.md +++ b/docs/customizing-volto-light-theme/concepts.md @@ -6,6 +6,7 @@ myst: "property=og:title": "Foundation, Concepts & Project Setup" "keywords": "Plone, Volto, Training, Volto Light Theme" --- + # Foundation, Concepts & Project Setup ## Volto Light Theme Core Concepts @@ -15,6 +16,7 @@ Volto Light Theme (VLT) is a customizable theme built for the Volto frontend of ### Base Styling VLT is designed with simplicity and a minimal aesthetic in mind. The three core principles are: + - **Consistency**: Predictable design patterns across all components - **Accessibility**: WCAG compliant with contrast checkers and semantic HTML - **Intuitiveness**: Clear visual hierarchy and user-friendly interfaces @@ -22,6 +24,7 @@ VLT is designed with simplicity and a minimal aesthetic in mind. The three core ### Customizable Variables VLT offers a set of CSS custom properties (variables) that allow developers to customize various design elements: + - **Colors**: Using paired foreground/background color system - **Spatial relationships**: Container widths and spacing scales - **Layouts**: Three-width container system @@ -93,23 +96,23 @@ While the system can be extended with non-color CSS properties, the default four config.blocks.themes = [ { style: { - '--theme-color': '#fff', - '--theme-high-contrast-color': '#ecebeb', - '--theme-foreground-color': '#000', - '--theme-low-contrast-foreground-color': '#555555', + "--theme-color": "#fff", + "--theme-high-contrast-color": "#ecebeb", + "--theme-foreground-color": "#000", + "--theme-low-contrast-foreground-color": "#555555", }, - name: 'default', - label: 'Default', + name: "default", + label: "Default", }, { style: { - '--theme-color': '#ecebeb', - '--theme-high-contrast-color': '#fff', - '--theme-foreground-color': '#000', - '--theme-low-contrast-foreground-color': '#555555', + "--theme-color": "#ecebeb", + "--theme-high-contrast-color": "#fff", + "--theme-foreground-color": "#000", + "--theme-low-contrast-foreground-color": "#555555", }, - name: 'grey', - label: 'Grey', + name: "grey", + label: "Grey", }, ]; ``` @@ -122,9 +125,9 @@ VLT uses three types of container widths: ```scss // Three-width layout system ---layout-container-width: 1440px; // for major elements like headers & large Blocks ---default-container-width: 940px; // balanced content presentation for most Blocks ---narrow-container-width: 620px; // optimal readability for text +--layout-container-width: 1440px; // for major elements like headers & large Blocks +--default-container-width: 940px; // balanced content presentation for most Blocks +--narrow-container-width: 620px; // optimal readability for text ``` The VLT `BlockWidthWidget` stores the value of the custom property `--block-width` so that it can be used by the StyleWrapper when injecting styles into the markup. @@ -145,58 +148,115 @@ We recommend creating your Plone project with **Cookieplone**. Our comprehensive ## Installing Volto Light Theme +VLT is shipped as two add-ons that you must install together: + +- the frontend Volto add-on `@kitconcept/volto-light-theme`, which is also a theme add-on, +- the backend Plone add-on `kitconcept.voltolighttheme`, which provides the site customization behaviors. + +```{note} +This training targets the VLT 8 line, which is currently released as alpha (`8.0.0-alpha.x` on npm, `8.0.0a.x` on PyPI), and is the version documented in the [official VLT install guide](https://volto-light-theme.readthedocs.io/how-to-guides/install.html). +If you need a stable release instead, use the latest 7.x version of both packages and skip the "install the recommended add-ons as dependencies" step, since 7.x still ships them as `peerDependencies`. +``` + ### Step 1: Install VLT and Recommended Block Add-ons -Navigate to the `frontend/packages/my-vlt-project` folder and install VLT: +VLT is installed like any other Volto add-on, as a dependency of your project add-on in {file}`frontend/packages/my-vlt-project/package.json`: + +```json +{ + "dependencies": { + "@kitconcept/volto-light-theme": "^8.0.0-alpha.31" + } +} +``` + +From the root of your project, you can let pnpm add it to the workspace package for you: ```bash -pnpm install @kitconcept/volto-light-theme@latest +pnpm --filter my-vlt-project add @kitconcept/volto-light-theme@alpha ``` -Volto Light Theme comes with several pre-configured add-ons that provide basic blocks for your website. If you'd like to include them, you can add them in the `addons` section in your {file}`package.json`, but this is not required. +Volto Light Theme supports all core blocks, and it also supports blocks coming from a set of recommended add-ons that provide the basic blocks for your website. +Including them is not required, and you can pick only the ones you want to use. -Here is the list of recommended addons to install, including VLT, which should be the last element: +Since VLT 8.0.0, these recommended add-ons are no longer declared as `peerDependencies` of VLT, so you have to install them yourself as dependencies of your project add-on in {file}`frontend/packages/my-vlt-project/package.json`: ```json -"addons": [ - "@eeacms/volto-accordion-block", - "@kitconcept/volto-button-block", - "@kitconcept/volto-heading-block", - "@kitconcept/volto-highlight-block", - "@kitconcept/volto-introduction-block", - "@kitconcept/volto-separator-block", - "@kitconcept/volto-slider-block", - "@kitconcept/volto-light-theme" -], +{ + "dependencies": { + "@eeacms/volto-accordion-block": "^12.0.0", + "@kitconcept/volto-banner-block": "^1.2.0", + "@kitconcept/volto-bm3-compat": "^1.0.0-alpha.1", + "@kitconcept/volto-button-block": "5.0.0-alpha.2", + "@kitconcept/volto-calendar-block": "^1.0.0-alpha.9", + "@kitconcept/volto-carousel-block": "^3.0.0-alpha.1", + "@kitconcept/volto-dsgvo-banner": "^4.0.0-alpha.2", + "@kitconcept/volto-heading-block": "^2.5.0", + "@kitconcept/volto-highlight-block": "^5.0.0-alpha.2", + "@kitconcept/volto-introduction-block": "^1.4.1", + "@kitconcept/volto-logos-block": "^4.0.0-alpha.1", + "@kitconcept/volto-separator-block": "^5.0.0-alpha.0", + "@kitconcept/volto-slider-block": "^7.0.0-alpha.1", + "@plonegovbr/volto-social-media": "^3.0.0-alpha.0", + "@kitconcept/volto-light-theme": "^8.0.0-alpha.31" + } +} +``` + +```{note} +The versions above are the known good versions at the time of writing. +The source of truth, up-to-date list lives in the [recommended add-ons reference](https://volto-light-theme.readthedocs.io/reference/recommended-addons.html) of the VLT documentation. ``` -While in your project package folder, add VLT and the block addons to the `addons` list in your `package.json`: +Installing a package is not enough: it also has to be declared as a Volto add-on in the `addons` key of the same {file}`package.json`, with VLT as the last element of the list: ```json { - "name": "my-vlt-project", "addons": [ "@eeacms/volto-accordion-block", + "@kitconcept/volto-banner-block", + "@kitconcept/volto-bm3-compat", "@kitconcept/volto-button-block", + "@kitconcept/volto-carousel-block", + "@kitconcept/volto-dsgvo-banner", "@kitconcept/volto-heading-block", "@kitconcept/volto-highlight-block", "@kitconcept/volto-introduction-block", + "@kitconcept/volto-logos-block", "@kitconcept/volto-separator-block", "@kitconcept/volto-slider-block", + "@plonegovbr/volto-social-media", "@kitconcept/volto-light-theme" ] } ``` -**Important:** VLT must be the last addon in the list to ensure proper style cascade. Your project addon will still be the last applied if defined in `volto.config.js`. +-**Important:** VLT must be the last addon in the list to ensure proper style cascade. Your project addon will still be the last applied if defined in `volto.config.js`. + +Run `pnpm install` from the project root after editing {file}`package.json` by hand, so that the workspace picks up the new dependencies. ### Step 2: Configure VLT as the Theme Provider -Open the `volto.config.js` file in your `frontend` folder and modify it as shown below: +VLT is not only a regular add-on, it is also a theme add-on, so it has to be declared as the theme of your project. +How you do that depends on your Volto version. + +For Volto 18.29.1 or later, and 19.0.0-alpha.10 or later, declare it with the `theme` key of your project add-on {file}`frontend/packages/my-vlt-project/package.json`, next to the `addons` key: + +```json +{ + "addons": [ + ..., + "@kitconcept/volto-light-theme" + ], + "theme": "@kitconcept/volto-light-theme" +} +``` + +For older Volto versions, open the {file}`volto.config.js` file in your `frontend` folder and declare the theme there: ```javascript -const addons = ['my-vlt-project']; -const theme = '@kitconcept/volto-light-theme'; +const addons = ["my-vlt-project"]; +const theme = "@kitconcept/volto-light-theme"; module.exports = { addons, @@ -214,16 +274,22 @@ In your backend folder, install the Python package for site customization behavi Edit `backend/pyproject.toml` and add to the dependencies array: -``` +```toml dependencies = [ - "Products.CMFPlone==6.1.3", + "Products.CMFPlone==6.2.1", "plone.api", "plone.restapi", "plone.volto", - "kitconcept.voltolighttheme==7.3.1", + "kitconcept.voltolighttheme==8.0.0a31", ] ``` +Then install the dependency from your backend folder: + +```bash +make install +``` + ### Step 4: Install the Backend Add-on Start your development environment: @@ -250,6 +316,9 @@ To enable site customization through the UI: 2. In the "Behaviors" tab, activate the desired behaviors 3. Click "Save" +These behaviors let you customize the header, footer, and theme of your Plone site with knobs on the content type they are applied to, either the Plone site or a subsite. +See the [site customization guide](https://volto-light-theme.readthedocs.io/conceptual-guides/site-customization.html) for the details of each behavior. + Now your project should have the VLT Site configurations available. ## File Structure Setup @@ -270,7 +339,7 @@ src/ └── _site.scss ``` -Create the files: +Create the files in `frontend/paackages/my-vlt-project` : ```bash cd src @@ -280,4 +349,4 @@ touch index.ts touch theme/_main.scss theme/_site.scss ``` -Remember that if you add new files to your project, it will be necessary to restart your Plone frontend. \ No newline at end of file +Remember that if you add new files to your project, it will be necessary to restart your Plone frontend. From 96f30ce06252df5f09aaa1fd382a2f5cdfe4dc38 Mon Sep 17 00:00:00 2001 From: Tishasoumya-02 Date: Sat, 29 Aug 2026 16:58:48 -0500 Subject: [PATCH 07/10] update widgets and components docs --- .../block-development-widgets.md | 347 +++++++++++++----- 1 file changed, 251 insertions(+), 96 deletions(-) diff --git a/docs/customizing-volto-light-theme/block-development-widgets.md b/docs/customizing-volto-light-theme/block-development-widgets.md index cc5016326..6112760d8 100644 --- a/docs/customizing-volto-light-theme/block-development-widgets.md +++ b/docs/customizing-volto-light-theme/block-development-widgets.md @@ -37,6 +37,52 @@ Controls content alignment within blocks: } ``` +### ColorSwatch Widget + +Lets editors pick from a curated palette instead of entering free-form values. +Each entry follows the `StyleDefinition` type from `@plone/types`, and you should always provide a `default` option so the field has a predictable fallback: + +```javascript +{ + widget: 'colorSwatch', + title: 'Background color', + default: 'default', + colors: [ + { + name: 'default', + label: 'Default', + style: { + '--theme-color': '#fff', + '--theme-foreground-color': '#000', + }, + }, + { + name: 'grey', + label: 'Grey', + style: { + '--theme-color': '#ecebeb', + '--theme-foreground-color': '#000', + }, + }, + ], +} +``` + +The widget stores the chosen color's `name` token, and the StyleWrapper adds that token as a CSS class on the block, so you can target it in your stylesheets. +If you also want the CSS custom properties injected inline, register a `styleFieldDefinition` utility for the field name used in the schema: + +```javascript +config.registerUtility({ + name: "myColorField", + type: "styleFieldDefinition", + method: (props) => colors, +}); +``` + +```{note} +This is the recommended way to use this widget, since it decouples the styles from the CSS and keeps a single source of truth for the color definitions. +``` + ### ThemeColorSwatch Widget Allows selection from configured themes stored in `config.blocks.themes`: @@ -61,6 +107,116 @@ Allows introducing a list of ordered objects with drag and drop: } ``` +### ColorPicker Widget + +A real color picker, with an RGB visual color chooser and a `hex` color field: + +```javascript +{ + widget: 'colorPicker', + title: 'Custom color', +} +``` + +### color_picker Widget + +A Semantic UI-free drop-in replacement that overrides Volto's `color_picker` widget. +Given an array of color definitions, it displays the colors that editors can choose: + +```javascript +{ + widget: 'color_picker', + title: 'Color', + colors: [ + { name: 'default', label: 'Default' }, + { name: 'grey', label: 'Grey' }, + ], +} +``` + +### Size Widget + +Selects the block size from a default list of values, one of either `small`, `medium`, or `large`: + +```javascript +{ + widget: 'size', + title: 'Size', + default: 'medium', +} +``` + +Like the BlockAlignment widget, it is based on the Buttons component under the hood, so its actions and the styles they apply are configurable. + +### SoftText and SoftTextarea Widgets + +`softTextWidget` and `softTextareaWidget` behave like the `text` and `textarea` widgets, but they display a real-time character count while typing. +When the count exceeds the limit set in `softMaxLength`, a notification appears, but the editor is still allowed to save the content. + +These widgets are configured from the backend, with `directives.widget` and its `frontendOptions`: + +```python +directives.widget( + "seo_title", + frontendOptions={ + "widget": "softTextWidget", + "widgetProps": {"softMaxLength": "55"}, + }, +) +seo_title = schema.TextLine( + title="SEO Title", + description="Override the meta title. Use maximum 55 characters.", + required=False, +) +``` + +### ColorContrastChecker Component + +Not a widget itself, but a component that calculates the contrast ratio between two colors following the WCAG accessibility guidelines. +Add it after a color input field in your own widget to warn the editor in real time about insufficient contrast: + +```jsx +import ContrastChecker from "./ContrastChecker"; + +const MyColorWidget = (props) => { + return ( + <> + + + + ); +}; + +export default MyColorWidget; +``` + +It accepts hex color codes, and compares the value of the field against its paired color. +The pairings and their defaults are defined in `config.settings.colorMap`: + +```javascript +config.settings.colorMap = { + primary_color: { + colorPair: "primary_foreground_color", + default: "#ffffff", + }, + primary_foreground_color: { + colorPair: "primary_color", + default: "#000000", + }, +}; +``` + +### Buttons Component + +Another helper rather than a widget, used to build widgets that show a list of buttons where a single value can be toggled. +The BlockAlignment and Size widgets are built on top of it. +You can pass it a configurable list of `actions`, along with the icon and the i18n message used for each one in `actionsInfoMap`, and filter out the default actions you don't want with `filterActions`. + +```{note} +As of VLT 8.0.0-alpha.5 these components were moved to the Volto core package. +If you are on Volto 19.0.0-alpha.12 or later, use the ones from Volto core instead of the ones provided by VLT. +``` + ## Creating a Custom Hero Block Let's build a hero block step by step, starting with a basic implementation and then enhancing it with VLT widgets. @@ -70,24 +226,24 @@ Let's build a hero block step by step, starting with a basic implementation and Create `src/components/blocks/myHero/schema.ts`: ```javascript -import { defineMessages } from 'react-intl'; +import { defineMessages } from "react-intl"; const messages = defineMessages({ hero: { - id: 'Hero', - defaultMessage: 'Hero', + id: "Hero", + defaultMessage: "Hero", }, title: { - id: 'Title', - defaultMessage: 'Title', + id: "Title", + defaultMessage: "Title", }, subtitle: { - id: 'Subtitle', - defaultMessage: 'Subtitle', + id: "Subtitle", + defaultMessage: "Subtitle", }, backgroundImage: { - id: 'Background Image', - defaultMessage: 'Background Image', + id: "Background Image", + defaultMessage: "Background Image", }, }); @@ -98,29 +254,29 @@ const heroBlockSchema = (props) => { title: intl.formatMessage(messages.hero), fieldsets: [ { - id: 'default', - title: 'Default', - fields: ['title', 'subtitle'], + id: "default", + title: "Default", + fields: ["title", "subtitle"], }, { - id: 'design', - title: 'Design', - fields: ['backgroundImage'], + id: "design", + title: "Design", + fields: ["backgroundImage"], }, ], properties: { title: { title: intl.formatMessage(messages.title), - type: 'string', + type: "string", }, subtitle: { title: intl.formatMessage(messages.subtitle), - type: 'string', + type: "string", }, backgroundImage: { title: intl.formatMessage(messages.backgroundImage), - widget: 'object_browser', - mode: 'image', + widget: "object_browser", + mode: "image", allowExternals: false, }, }, @@ -136,28 +292,28 @@ export { heroBlockSchema }; Create `src/components/blocks/myHero/View.tsx`: ```tsx -import React from 'react'; -import cx from 'classnames'; -import config from '@plone/volto/registry'; -import { flattenToAppURL, isInternalURL } from '@plone/volto/helpers/Url/Url'; -import type { BlockViewProps } from '@plone/types'; +import React from "react"; +import cx from "classnames"; +import config from "@plone/volto/registry"; +import { flattenToAppURL, isInternalURL } from "@plone/volto/helpers/Url/Url"; +import type { BlockViewProps } from "@plone/types"; const HeroView = (props: BlockViewProps) => { const { className, style } = props; const { title, subtitle, backgroundImage, url } = props?.data || {}; - const hasImage = backgroundImage?.[0]?.['@id']; + const hasImage = backgroundImage?.[0]?.["@id"]; let renderedImage = null; if (hasImage) { - const Image = config.getComponent('Image').component; + const Image = config.getComponent("Image").component; const imageItem = backgroundImage[0]; if (Image) { renderedImage = ( { renderedImage = ( { return (
{hasImage &&
{renderedImage}
} @@ -206,13 +362,13 @@ export default HeroView; Create `src/components/blocks/myHero/Edit.tsx`: ```tsx -import React from 'react'; -import { useIntl } from 'react-intl'; -import SidebarPortal from '@plone/volto/components/manage/Sidebar/SidebarPortal'; -import { BlockDataForm } from '@plone/volto/components/manage/Form'; -import { heroBlockSchema } from './schema'; -import HeroView from './View'; -import type { BlockEditProps } from '@plone/types'; +import React from "react"; +import { useIntl } from "react-intl"; +import SidebarPortal from "@plone/volto/components/manage/Sidebar/SidebarPortal"; +import { BlockDataForm } from "@plone/volto/components/manage/Form"; +import { heroBlockSchema } from "./schema"; +import HeroView from "./View"; +import type { BlockEditProps } from "@plone/types"; const HeroEdit = (props: BlockEditProps) => { const { selected, onChangeBlock, block, data } = props; @@ -249,28 +405,28 @@ export default HeroEdit; Update `src/config/blocks.ts`: ```typescript -import type { ConfigType } from '@plone/registry'; -import HeroView from '../components/blocks/myHero/View'; -import HeroEdit from '../components/blocks/myHero/Edit'; -import { heroBlockSchema } from '../components/blocks/myHero/schema'; -import heroSVG from '@plone/volto/icons/hero.svg'; +import type { ConfigType } from "@plone/registry"; +import HeroView from "../components/blocks/myHero/View"; +import HeroEdit from "../components/blocks/myHero/Edit"; +import { heroBlockSchema } from "../components/blocks/myHero/schema"; +import heroSVG from "@plone/volto/icons/hero.svg"; export default function install(config: ConfigType) { // ... block themes configuration ... // Register Hero Block config.blocks.blocksConfig.hero = { - id: 'hero', - title: 'Hero', + id: "hero", + title: "Hero", icon: heroSVG, - group: 'common', + group: "common", view: HeroView, edit: HeroEdit, restricted: false, mostUsed: true, blockSchema: heroBlockSchema, sidebarTab: 1, - category: 'hero', + category: "hero", }; return config; @@ -340,7 +496,7 @@ Create `src/theme/blocks/_hero.scss`: &::after { position: absolute; background: rgba(0, 0, 0, 0.4); - content: ''; + content: ""; inset: 0; } } @@ -356,8 +512,8 @@ Create `src/theme/blocks/_hero.scss`: Import in `src/theme/_main.scss`: ```scss -@import './blocks/hero'; -@import './site'; +@import "./blocks/hero"; +@import "./site"; ``` ### Step 6: Enhance with VLT Widgets @@ -367,32 +523,32 @@ Now let's add VLT's powerful widgets to control block width and alignment. Updat Update `src/components/blocks/myHero/schema.ts`: ```javascript -import { defineMessages } from 'react-intl'; +import { defineMessages } from "react-intl"; const messages = defineMessages({ hero: { - id: 'Hero', - defaultMessage: 'Hero', + id: "Hero", + defaultMessage: "Hero", }, title: { - id: 'Title', - defaultMessage: 'Title', + id: "Title", + defaultMessage: "Title", }, subtitle: { - id: 'Subtitle', - defaultMessage: 'Subtitle', + id: "Subtitle", + defaultMessage: "Subtitle", }, backgroundImage: { - id: 'Background Image', - defaultMessage: 'Background Image', + id: "Background Image", + defaultMessage: "Background Image", }, blockWidth: { - id: 'Block Width', - defaultMessage: 'Block Width', + id: "Block Width", + defaultMessage: "Block Width", }, textAlignment: { - id: 'Text Alignment', - defaultMessage: 'Text Alignment', + id: "Text Alignment", + defaultMessage: "Text Alignment", }, }); @@ -403,29 +559,29 @@ const heroBlockSchema = (props) => { title: intl.formatMessage(messages.hero), fieldsets: [ { - id: 'default', - title: 'Default', - fields: ['title', 'subtitle'], + id: "default", + title: "Default", + fields: ["title", "subtitle"], }, { - id: 'design', - title: 'Design', - fields: ['backgroundImage'], + id: "design", + title: "Design", + fields: ["backgroundImage"], }, ], properties: { title: { title: intl.formatMessage(messages.title), - type: 'string', + type: "string", }, subtitle: { title: intl.formatMessage(messages.subtitle), - type: 'string', + type: "string", }, backgroundImage: { title: intl.formatMessage(messages.backgroundImage), - widget: 'object_browser', - mode: 'image', + widget: "object_browser", + mode: "image", allowExternals: false, }, }, @@ -437,21 +593,21 @@ const heroBlockSchema = (props) => { const heroSchemaEnhancer = ({ formData, schema, intl }) => { // Add custom fields to the styling schema at the beginning schema.properties.styles.schema.fieldsets[0].fields = [ - 'blockWidth:noprefix', - 'align', + "blockWidth:noprefix", + "align", ...schema.properties.styles.schema.fieldsets[0].fields, ]; - schema.properties.styles.schema.properties['blockWidth:noprefix'] = { - widget: 'blockWidth', + schema.properties.styles.schema.properties["blockWidth:noprefix"] = { + widget: "blockWidth", title: intl.formatMessage(messages.blockWidth), - default: 'default', + default: "default", }; schema.properties.styles.schema.properties.align = { - widget: 'blockAlignment', + widget: "blockAlignment", title: intl.formatMessage(messages.textAlignment), - default: 'center', + default: "center", }; return schema; @@ -465,26 +621,26 @@ export { heroBlockSchema, heroSchemaEnhancer }; Update `src/config/blocks.ts` to include the schema enhancer: ```typescript -import type { ConfigType } from '@plone/registry'; -import HeroView from '../components/blocks/myHero/View'; -import HeroEdit from '../components/blocks/myHero/Edit'; +import type { ConfigType } from "@plone/registry"; +import HeroView from "../components/blocks/myHero/View"; +import HeroEdit from "../components/blocks/myHero/Edit"; import { heroBlockSchema, heroSchemaEnhancer, -} from '../components/blocks/myHero/schema'; -import { composeSchema } from '@plone/volto/helpers/Extensions'; -import { defaultStylingSchema } from '@kitconcept/volto-light-theme/components/Blocks/schema'; -import heroSVG from '@plone/volto/icons/hero.svg'; +} from "../components/blocks/myHero/schema"; +import { composeSchema } from "@plone/volto/helpers/Extensions"; +import { defaultStylingSchema } from "@kitconcept/volto-light-theme/components/Blocks/schema"; +import heroSVG from "@plone/volto/icons/hero.svg"; export default function install(config: ConfigType) { // ... block themes configuration ... // Register Hero Block config.blocks.blocksConfig.hero = { - id: 'hero', - title: 'Hero', + id: "hero", + title: "Hero", icon: heroSVG, - group: 'common', + group: "common", view: HeroView, edit: HeroEdit, restricted: false, @@ -492,7 +648,7 @@ export default function install(config: ConfigType) { blockSchema: heroBlockSchema, schemaEnhancer: composeSchema(defaultStylingSchema, heroSchemaEnhancer), sidebarTab: 1, - category: 'hero', + category: "hero", }; return config; @@ -619,13 +775,12 @@ Create `src/theme/blocks/_relatedItems.scss`: } } } - ``` Import it in `_main.scss`: ```scss -@import './blocks/hero'; -@import './blocks/relatedItems'; -@import './site'; -``` \ No newline at end of file +@import "./blocks/hero"; +@import "./blocks/relatedItems"; +@import "./site"; +``` From 42e0cc974fa7a6bad264a87cbdec3df1156db1af Mon Sep 17 00:00:00 2001 From: Tishasoumya-02 Date: Sat, 29 Aug 2026 17:53:07 -0500 Subject: [PATCH 08/10] fix semantics --- .../block-development-widgets.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/customizing-volto-light-theme/block-development-widgets.md b/docs/customizing-volto-light-theme/block-development-widgets.md index 6112760d8..2c845b76b 100644 --- a/docs/customizing-volto-light-theme/block-development-widgets.md +++ b/docs/customizing-volto-light-theme/block-development-widgets.md @@ -225,7 +225,7 @@ Let's build a hero block step by step, starting with a basic implementation and Create `src/components/blocks/myHero/schema.ts`: -```javascript +```typescript import { defineMessages } from "react-intl"; const messages = defineMessages({ @@ -460,9 +460,9 @@ Create `src/theme/blocks/_hero.scss`: width: 100%; height: 100%; flex-direction: column; - align-items: var(--align--block-alignment); + align-items: var(--block-alignment); gap: 1rem; - text-align: var(--align--block-alignment); + text-align: var(--block-alignment); .hero-title { margin-bottom: $spacing-small; @@ -522,7 +522,7 @@ Now let's add VLT's powerful widgets to control block width and alignment. Updat Update `src/components/blocks/myHero/schema.ts`: -```javascript +```typescript import { defineMessages } from "react-intl"; const messages = defineMessages({ @@ -685,7 +685,7 @@ Update `src/theme/blocks/_hero.scss` to use the CSS custom properties set by the position: absolute; display: flex; flex-direction: column; - align-items: var(--align--block-alignment); + align-items: var(--block-alignment); width: 100%; height: 100%; padding: 4rem; @@ -713,7 +713,7 @@ Let's learn how to integrate the `@plone-collective/volto-relateditems-block` in ### Install the Block -To install the related items block, make sure you are in the `frontend/packages/volto-my-project` folder, and use the following command: +To install the related items block, make sure you are in the `frontend/packages/my-vlt-project` folder, and use the following command: ```bash pnpm install @plone-collective/volto-relateditems-block@latest @@ -724,12 +724,18 @@ Add it to your `package.json` addons (before VLT): ```json "addons": [ "@eeacms/volto-accordion-block", + "@kitconcept/volto-banner-block", + "@kitconcept/volto-bm3-compat", "@kitconcept/volto-button-block", + "@kitconcept/volto-carousel-block", + "@kitconcept/volto-dsgvo-banner", "@kitconcept/volto-heading-block", "@kitconcept/volto-highlight-block", "@kitconcept/volto-introduction-block", + "@kitconcept/volto-logos-block", "@kitconcept/volto-separator-block", "@kitconcept/volto-slider-block", + "@plonegovbr/volto-social-media", "@plone-collective/volto-relateditems-block", "@kitconcept/volto-light-theme" ], From 50e9df57751d87338101605d2828ebff26c55ffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dante=20=C3=81lvarez?= <89805481+danalvrz@users.noreply.github.com> Date: Tue, 15 Sep 2026 17:37:34 +0200 Subject: [PATCH 09/10] vlt 8 draft --- .../advanced-components-bm3.md | 870 ++++++++++++------ .../block-development-widgets.md | 661 ++++++------- .../customizing-volto-light-theme/concepts.md | 371 +++++++- .../design-system-implementation.md | 298 ++++-- docs/customizing-volto-light-theme/index.md | 2 + .../question-answer.md | 86 +- styles/config/vocabularies/Plone/accept.txt | 1 + 7 files changed, 1559 insertions(+), 730 deletions(-) diff --git a/docs/customizing-volto-light-theme/advanced-components-bm3.md b/docs/customizing-volto-light-theme/advanced-components-bm3.md index c463e6936..87ef67539 100644 --- a/docs/customizing-volto-light-theme/advanced-components-bm3.md +++ b/docs/customizing-volto-light-theme/advanced-components-bm3.md @@ -16,18 +16,33 @@ The Card primitive is VLT's reusable component for displaying content in card la ### Card Structure ```jsx - - + + -

Title

-

Summary text goes here.

+

ARM-7 Bench Arm

+

Six-axis arm for precision bench work.

- +
``` +### Making a Card Clickable + +A card becomes a link when you give it either an `item` or an `href`. The two are mutually exclusive, and `item` is the one to reach for when you have a content object: + +```jsx +... // preferred: a Plone content object +... // for arbitrary destinations +``` + +Passing `item` lets the underlying `UniversalLink` inspect the content type, so a File links to its download URL and an Image to its view, rather than to the object's default page. +Passing `href={item['@id']}` skips that, and those types end up linking to the wrong place. +Use `href` when you genuinely have only a URL. + +Pass `null` to make the card non-interactive, which is what listing templates do in edit mode. + ### Card Variations - **Vertical layout** (default): Image on top @@ -37,18 +52,18 @@ The Card primitive is VLT's reusable component for displaying content in card la ### Card.Image Slot -Display an image using a `src` prop or use a Plone image object: +Point the slot at a content object and it resolves the image from that item: ```tsx - + ``` -Custom image component: +A `src` prop works too, for an image that is not a content object. To control how the image is rendered, pass your own component: ```tsx ``` @@ -69,6 +84,10 @@ const Summary = config.getComponent({ ``` +`Card` generates an id for the card's accessible name and builds a link component for the item, then passes both down to its slots as the `a11yLabelId` and `LinkToItem` props. +Slots forward them to their children, so a Summary rendered inside `Card.Summary` receives them without you wiring anything up. +Using them is what gives the card an accessible name and a real link—see the next section. + ## Creating Custom Summary Components The Summary component displays content metadata in listings, teasers, and cards. VLT includes built-in implementations: @@ -77,100 +96,191 @@ The Summary component displays content metadata in listings, teasers, and cards. - `NewsItemSummary`: Publication date, kicker, title, description - `EventSummary`: Start/end date, kicker, title, description - `FileSummary`: File size and type +- `PersonSummary`: Contact details for the Person content type + +### The Summary Contract + +Every Summary receives the same props. Getting this shape right matters, because three of them are easy to miss: + +```ts +type DefaultSummaryProps = { + item: Partial; // the content object + LinkToItem?: React.ElementType; // wraps the title in the card's link + HeadingTag?: React.ElementType; // the heading level to render + a11yLabelId?: string; // id that names the card + hide_description?: boolean; +}; +``` + +`item` is the content object. The other three are **siblings of `item`, not fields inside it**. A common mistake is to read them from `item`, where they will always be `undefined`. + +`DefaultSummary` shows the pattern to follow: + +```tsx +const DefaultSummary = (props) => { + const { + item, + LinkToItem = React.Fragment, + HeadingTag = 'div', + a11yLabelId, + } = props; + + return ( + <> + {item?.head_title &&
{item.head_title}
} + + {item.title ? item.title : item.id} + + {/* ... */} + + ); +}; +``` + +The `id={a11yLabelId}` and the `` wrapper are not decoration. +Together they give the card an accessible name and put a real link in the heading, which is how a keyboard or screen reader user reaches the item. +A Summary that omits them renders a card that looks right but cannot be navigated. -### Step 1: Create Product Content Type +### Step 1: Create the Robot Content Type -First, create a custom "Product" content type through the Plone UI: +The Robotarium lends robots, so it needs a content type for them. Create it through the Plone UI: 1. Go to http://localhost:3000/controlpanel/dexterity-types 2. Click "Add New Content Type" 3. Fill in: - - **Type Name**: Product - - **Short Name**: product (auto-filled) - - **Description**: A product with pricing information + - **Type Name**: Robot + - **Description**: A robot available for booking 4. Click "Add" 5. In the "Behaviors" tab, enable: - - "Kicker" (we'll use this field to store the price) - - "Preview image" - - Any other desired behaviors (Dublin Core, etc.) + - **Kicker field**, which adds `head_title`. We will use it to store the robot's charge level. + - **Preview Image**, so robots can show a photo in listings. + - Any other behaviors you want, such as Dublin Core metadata. 6. Click "Save" +```{note} +The form asks only for a name and a description. Plone derives the type's id from the name by normalizing it, so **Robot** becomes `robot`, and a name like **Charging Station** would become `charging_station`. + +That id is what the REST API returns as the content's `@type`, and it is the value you register components against later in this chapter. It is worth confirming rather than assuming: open a robot you created and check the `@type` in `http://localhost:8080/Plone/`, or look at the URL of the type you just added in the control panel. + +Plone's built-in types predate this rule and keep their historical ids, which is why VLT registers its news summary against `News Item`, with a space and capitals, rather than `news_item`. +``` + ### Step 2: Create a Custom Summary Component -Create `src/components/Summary/ProductSummary.tsx`: +Create `src/components/Summary/RobotSummary.tsx`: ```tsx +import * as React from 'react'; import { FormattedNumber } from 'react-intl'; +import type { DefaultSummaryProps } from '@kitconcept/volto-light-theme/components/Summary/DefaultSummary'; -const ProductSummary = ({ item }) => { +const RobotSummary = (props: DefaultSummaryProps) => { const { - title, - description, - HeadingTag = 'h3', - head_title, - currency = 'EUR', - } = item; - const price = parseFloat(head_title); + item, + LinkToItem = React.Fragment, + HeadingTag = 'div', + a11yLabelId, + } = props; + const { title, description, head_title } = item; + const charge = parseFloat(head_title); return ( <> - - {title ? title : item.id} + + {title ? title : item.id} - {description &&

{description}

} - {head_title && !isNaN(price) && ( -
- - {description}

} + {head_title && !isNaN(charge) && ( +
+
+ Charge + + + +
+ {/* The bar repeats the value the label already states, so it is + hidden from assistive technology rather than announced twice. */} + )} ); }; -ProductSummary.hideLink = false; -export default ProductSummary; +RobotSummary.hideLink = false; +export default RobotSummary; ``` -**Note**: We use the `head_title` field (kicker) to store price information. The price is formatted using `FormattedNumber` for proper currency display with EUR as default. In a real project, you would create custom fields for price and currency. +The `HeadingTag` default is `'div'` rather than a fixed heading level. The caller decides the level, because the right one depends on where the listing sits in the page's heading outline. + +**Note**: We are reusing the kicker (`head_title`) to hold the charge level, so that the example needs no new field. A real Robotarium would add a proper numeric field to the Robot type instead, and reserve the kicker for what it is meant for: a line of text above the title. + +### Step 3: Add Styles for the Robot Summary -### Step 3: Add Styles for Product Summary +The charge is the one thing VLT has no styling for, so it is the one thing this partial has to describe: a labelled row above a slim track whose fill is as wide as the charge. -Create `src/theme/_productSummary.scss`: +Create `src/theme/_robotSummary.scss`: ```scss -.products .card .card-summary { - padding-bottom: 0 !important; - .product-price { - font-size: 1.25rem; - color: var(--accent-color); - margin-top: 0.5rem; - - .price { - background: var(--theme-high-contrast-color); - padding: 0.25rem 0.75rem; - border-radius: 4px; - } +.robot-charge { + margin-top: $spacing-small; + + .charge-label { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; + letter-spacing: 0.14em; + text-transform: uppercase; + @include headtitle2(); + } + + .charge-track { + overflow: hidden; + height: 5px; + border-radius: 999px; + background: color-mix( + in oklab, + var(--theme-foreground-color) 12%, + transparent + ); } - .product-title { - margin-top: 0; + .charge-level { + height: 100%; + border-radius: 999px; + background: linear-gradient( + 90deg, + color-mix(in oklab, var(--accent-color) 60%, #fff), + var(--accent-color) + ); } } + +// Cards that carry a charge read as instrument panels, so give them an edge. +.card:has(.robot-charge) { + border: 1px solid oklch(1 0 0 / 0.28); +} ``` +Three decisions here are worth carrying to your own work. + +**Nothing is scoped to the listing.** A Summary is rendered in listings, in teasers, and in bare cards, and the same component should look the same in all of them. Scoping these rules to the fleet listing—`.robots .card .card-summary .robot-charge`—would style the listing and leave the same robot in a teaser looking like a different component. Selecting on what the Summary itself renders keeps the contexts in step. + Import in `src/theme/_main.scss`: ```scss -@import './blocks/hero'; -@import './blocks/relatedItems'; -@import './productSummary'; -@import './site'; +@import './blocks/button'; +@import './blocks/cover'; +@import './blocks/grid'; +@import './blocks/slider'; +@import './blocks/teaser'; +@import './robotSummary'; ``` ### Step 4: Register the Summary Component @@ -178,79 +288,87 @@ Import in `src/theme/_main.scss`: In `src/config/settings.ts`: ```typescript -import ProductSummary from '../components/Summary/ProductSummary'; +import RobotSummary from '../components/Summary/RobotSummary'; export default function install(config: ConfigType) { // ... previous config ... config.registerComponent({ name: 'Summary', - component: ProductSummary, - dependencies: ['product'], + component: RobotSummary, + dependencies: ['robot'], }); return config; } ``` -### Step 5: Test the Product Summary +### Step 5: Test the Robot Summary -1. Create a Product item in your site -2. Fill in the title, description, and kicker field (e.g., "$99.99") -3. Add the Product to a Listing or Teaser block -4. The custom ProductSummary will display the price, title, and description +1. Add a Robot to your site, for example **ARM-7 Bench Arm** +2. Fill in the title, a short description, and the kicker with a plain number such as `87` +3. Add the robot to a Listing or Teaser block +4. `RobotSummary` renders the title, description, and a charge level of 87% ## Creating Custom Listing Variations with Card Actions -Listing variations customize content display in Listing blocks. Let's create a ProductTemplate demonstrating the Card.Actions slot for interactive buttons. +Listing variations customize content display in Listing blocks. Let's build a `RobotsTemplate` that presents the fleet and uses the Card.Actions slot for a booking button. ### Card.Actions Slot +```{important} +`Card.Actions` only renders what a template puts in it, and **none of VLT's built-in listing variations put anything there**. Registering an `Actions` component is not enough on its own: List, List with images, Grid, and the Teaser block all ignore the slot, so a card rendered by any of them shows no actions. + +That is why the fleet listing below needs its own template. If you register an Actions component and see nothing, check which variation the listing is using before suspecting the registration. +``` + The Card.Actions slot provides interactive elements beyond the main card link: -- "Add to Cart" or "Get Now" buttons for products -- "Download" buttons for files -- "Register" buttons for events +- "Book this unit" for a robot that is available +- "Download spec sheet" for its documentation +- "Reserve a slot" for a robot that is currently out -### Step 1: Create ProductActions Component +### Step 1: Create RobotActions Component -Create `src/components/Actions/ProductActions.tsx`: +Create `src/components/Actions/RobotActions.tsx`: ```tsx -const ProductActions = ({ item }) => { +const RobotActions = ({ item }) => { return ( - <> - - + ); }; -export default ProductActions; +export default RobotActions; ``` -### Step 2: Register ProductActions +The button carries `type="button"` so it does not submit a surrounding form, and names the robot it belongs to. In a fleet listing, four buttons all reading "Book" are indistinguishable to someone moving through the page control by control; "Book ARM-7 Bench Arm" is not. + +### Step 2: Register RobotActions Update `src/config/settings.ts`: ```typescript -import ProductActions from '../components/Actions/ProductActions'; +import RobotActions from '../components/Actions/RobotActions'; export default function install(config: ConfigType) { // ... previous configuration ... - // Register ProductActions component + // Register RobotActions component config.registerComponent({ name: 'Actions', - component: ProductActions, - dependencies: ['product'], + component: RobotActions, + dependencies: ['robot'], }); return config; } ``` -### Step 3: Create ProductTemplate Listing Variation +### Step 3: Create RobotsTemplate Listing Variation -Create `src/components/blocks/Listing/ProductTemplate.tsx`: +Create `src/components/blocks/Listing/RobotsTemplate.tsx`: ```tsx import React from 'react'; @@ -262,7 +380,7 @@ import config from '@plone/volto/registry'; import DefaultSummary from '@kitconcept/volto-light-theme/components/Summary/DefaultSummary'; import cx from 'classnames'; -const ProductTemplate = ({ items, linkTitle, linkHref, isEditMode }) => { +const RobotsTemplate = ({ items, linkTitle, linkHref, isEditMode }) => { let link = null; let href = linkHref?.[0]?.['@id'] || ''; const PreviewImageComponent = config.getComponent('PreviewImage').component; @@ -279,7 +397,7 @@ const ProductTemplate = ({ items, linkTitle, linkHref, isEditMode }) => { return ( <> -
+
    {items.map((item) => { const Summary = config.getComponent({ @@ -293,47 +411,52 @@ const ProductTemplate = ({ items, linkTitle, linkHref, isEditMode }) => { }).component; const showLink = !Summary.hideLink && !isEditMode; + const placeholderSrc = + config.settings.placeholderImages?.[item['@type']]; return ( -
    - - {item.image_field !== '' && ( + + {(item.image_field !== '' || placeholderSrc) && ( )} - + {Actions && } -
    + ); })} -
+ {link &&
{link}
} ); }; -ProductTemplate.propTypes = { +RobotsTemplate.propTypes = { items: PropTypes.arrayOf(PropTypes.any).isRequired, linkTitle: PropTypes.string, linkHref: PropTypes.any, isEditMode: PropTypes.bool, }; -export default ProductTemplate; +export default RobotsTemplate; ``` **Key Features:** @@ -341,24 +464,35 @@ export default ProductTemplate; - `showLink` ensures cards are only clickable when appropriate - `isEditMode` disables navigation during editing - Dynamically renders Actions only for content types that have them registered +- Renders a `