diff --git a/.design-sync/NOTES.md b/.design-sync/NOTES.md new file mode 100644 index 0000000..c94764a --- /dev/null +++ b/.design-sync/NOTES.md @@ -0,0 +1,209 @@ +# design-sync notes — sc-volto-light-theme + +State as of 2026-09-02. No sync has completed yet, but the converter pipeline **works**: +bundle, previews and CSS all build, and 13 of 17 components render. The target project +`sc-volto-light-theme` (`f0a26637-18ca-4f24-80e7-8d196207efcc`) exists and is empty — +created this session and pinned in `config.json`, so a retry repairs it rather than +orphaning it. + +An earlier revision of this file said the converter path was out of reach. That was wrong, +and the correction is the whole point of the recipe below. + +## The recipe + +Volto is a webpack application, not a publishable component package. Five things bridge +that gap; none of them forks the converter. + +### 1. A synthetic `node_modules` that encodes Volto's alias scheme + +`webpack-plugins/webpack-relative-resolver.js` rewrites `@plone/volto/X` to +`@plone/volto/src/X` at build time, so `@plone/volto/components/theme/Icon/Icon` exists on +disk only under `src/`. esbuild resolves node-style and finds nothing — 121 errors. + +`.design-sync/nm-overlay/` is a directory of symlinks that encodes the rewrite: the three +aliased packages point at their `src/`, everything else passes through. Sources are the +add-on's own `node_modules`, the app's, **and Volto's** (`react-redux`, +`semantic-ui-react`, `react-router-dom` and `react-toastify` resolve only from there). +Built by the script in the scratchpad recipe below; rebuild it after any `pnpm install`. + +Aliased: `@plone/volto` → `core/packages/volto/src`, `@plone/volto-slate` → +`core/packages/volto-slate/src`, `@kitconcept/volto-light-theme` → its `src` **at the +declared version** — the pnpm store holds several and a bare glob picks alpha.22 over +alpha.31. + +### 2. A curated entry, because the package exports no components + +`src/index.ts` exports only `applyConfig`. `src/storybook/dsEntry.ts` re-exports the 17 +storied view components under the names their storybook titles use. + +**QuoteBlock is excluded on purpose**: its view imports `@plone/volto-slate/blocks/Text`, +whose graph reaches the slate editor's `.less` stylesheets, and the converter's esbuild has +no `.less` loader. The same trap caught `config/blocks` — see §5. + +### 3. `.d.ts`, emitted rather than shipped + +The converter reads the component roster from `/index.d.ts`; it resolves the package +through its realpath, so a `types` field in an overlay manifest is **not** consulted. + +```bash +cd frontend/packages/volto-light-theme +./node_modules/.bin/tsc --emitDeclarationOnly --declaration --noEmit false \ + --skipLibCheck --outDir ./dist -p tsconfig.json +``` + +Exits 1 with ~173 TS2307 — the known pnpm false positives — and **emits all 146 files +anyway**. `index.d.ts` at the package root re-exports `./dist/src/storybook/dsEntry`. +Both are gitignored build products. + +### 4. CSS, compiled on purpose + +Volto compiles the SCSS and style-loader injects it at runtime, so the storybook build +contains no `.css` file to scrape (`[CSS_RUNTIME]`). `.design-sync/build-css.sh` compiles +upstream VLT's theme plus ours into `dist/theme.css` (488 KB), stubbing the two webpack +virtuals sass can't resolve (`addonsThemeCustomizationsVariables`, `…Main`). `cfg.cssEntry` +points at it. + +### 5. Runtime context the app normally provides + +Three modules under `src/storybook/`, imported in order by `dsEntry`: + +- **`dsGlobals.ts`** — razzle defines `__CLIENT__`, `__SERVER__`, `__DEVELOPMENT__` and + `__DEBUG__` through webpack's DefinePlugin. Without them the first component to touch one + throws `ReferenceError: __CLIENT__ is not defined`. **This single fix took the render + check from 4/17 to 13/17.** +- **`dsConfig.ts`** — seeds `config.settings`. Note `installSettings` spreads the existing + `apiExpanders` and writes into `settings.vlt.components`, so both must exist first or it + throws on its first statement and every later setting is silently skipped. + `@plone/volto/config` cannot be imported: it reaches `load-volto-addons` (a webpack + virtual) and node builtins. +- **`DsPreviewProvider.tsx`** — `cfg.provider`, because the real decorators import + `@root/../locales/en.json`. Reproduces `withTheme`'s defaults plus `IntlProvider` and + `MemoryRouter`. + +### 6. Run the converter from the storybook project root + +`index.json` `importPath`s are relative to `frontend/core/packages/volto` — that is where +`pnpm --filter @plone/volto build-storybook` runs. The converter resolves them against the +`.storybook` parent, the cwd, or the static dir's parent, so **run it with cwd = +`frontend/core/packages/volto`** and pass absolute paths. Without that: `story sources: +0/0`; with it: `103/103`. + +The reference storybook itself builds clean (124 entries, 28 titles): + +```bash +cd frontend && VOLTOCONFIG=$(pwd)/volto.config.js \ + pnpm --filter @plone/volto build-storybook -c $(pwd)/.storybook \ + -o "$(git rev-parse --show-toplevel)/.design-sync/sb-reference" +``` + +Do **not** use `npx storybook build`: the binary lives in the vendored Volto package, the +build needs `VOLTOCONFIG`, and `frontend/` has no `razzle.config.js`. + +## Where it stands + +`package-validate.mjs`: **13/17 render cleanly.** Outstanding: + +| component | cause | +|---|---| +| `Grid`, `Highlight` | `config.settings.downloadableObjects.includes(...)` inside Volto's `UniversalLink` — the *preview's* registry is empty (see below) | +| `Navigation` | `items.map` on undefined — same root cause | +| `MediaCarousel` | `SecurityError: … 'caches' … context is sandboxed` — an artifact of the capture sandbox, not the component | + +Also `[GRID_OVERFLOW]` on HeroBlock, MainImageBlock, HeaderBarActions and MenuItem +(`cardMode: "column"`, a config fix), `[RENDER_THIN]` on ThemeToggle, `[TOKENS_MISSING]` +for 11 quanta tokens Volto injects at runtime, and `[FONT_DANGLING]` for two upstream +`@font-face` rules whose files were not copied. + +### The one unsolved blocker: two config registries + +`@plone/registry` ends with `const instance = new Config(); Object.freeze(instance);` — a +**module-local** singleton. The converter bundles Volto twice: once into `_ds_bundle.js` +and again into each compiled preview (story imports resolve components to the bundle +global, but everything else bundles from source). Each copy constructs its own empty +`Config`, and `dsConfig.ts` seeds only the bundle's. Components reading `config.settings` — +`UniversalLink`, which every listing card links through — throw in the preview no matter +how well the bundle is seeded. + +Shimming `@plone/registry` in the overlay to memoise the instance on `globalThis` +**does not work**: esbuild resolves it from the importer's own `node_modules` +(`frontend/core/packages/volto/node_modules/@plone/registry`) before consulting the +`--node-modules` path, so the shim is never reached. Verified — `__dsPloneRegistry` appears +in neither output bundle. + +Untried options, cheapest first: + +1. Exclude the three with `cfg.titleMap: {"": null}` — validate goes clean and the + sync ships the 14 components that render. +2. Shadow the package inside the vendored checkout + (`frontend/core/packages/volto/node_modules/@plone/registry`), where esbuild *does* + look. Reversible and gitignored, but any `pnpm install` undoes it. +3. Fork `lib/story-imports.mjs` — the seam explicitly built for per-repo resolution policy + and, unlike `bundle.mjs`, sanctioned for forking. + +`compare.mjs` — the fidelity oracle — has **not run yet**; the skill gates it behind a +clean validate. + +## [GENERAL] Icons — solved, and the two traps that hid the fix + +Volto imports icons as `.svg` and its webpack turns them into components via `svg-loader`. +The converter's esbuild loads `.svg` as a **data URL string** (`loader: {'.svg': 'dataurl'}` +in `lib/bundle.mjs`, not configurable), so `Icon` received a string and rendered an empty +`<svg>` — a blank box wherever a component draws an icon. + +The fix has three parts: + +1. `.design-sync/build-icons.py` generates a `<name>.svg.js` twin per icon, carrying the + `{attributes, content}` shape `Icon` reads. The parsing mirrors `svg-loader@0.0.2` + statement for statement. Both icon sets are covered: Volto's 335 and our 5. +2. The overlay points `@plone/volto/icons` at the generated directory (which deliberately + contains no `.svg` files — esbuild would find those first). +3. Our own five icons are reached through the package name rather than a relative path, + because **the overlay cannot intercept a relative import**. `ThemeToggle.tsx` and + `HoverReaderControls.tsx` now import + `@simplesconsultoria/volto-light-theme/icons/<name>.svg`, the same form + `.storybook/preview.jsx` already used, and `.design-sync/tsconfig.ds.json` maps that + subpath at the twins. esbuild honours tsconfig `paths` ahead of the overlay, which is + why the mapping — not a symlink — is what fixes it. + +**Two silent traps cost most of the debugging**, both now documented in +`tsconfig.ds.json` itself: + +- A `"//"` documentation *key* breaks the converter's comment-stripping regex, which + treats it as a line comment, mangles the JSON, and drops the paths mapping **without + printing anything**. Use a block comment. +- The path target must end in a bare `*`, never `*.js`. The plugin strips only a trailing + `*` and appends its own extension list, so `*.js` becomes a literal directory name, the + rule never matches, and the broader `/*` rule silently wins — sending icons straight back + to the real `.svg` files. + +Also worth knowing: `_x32_` and `M460.292` appear **inside the data URL** too, because +URL-encoding leaves digits and underscores alone. Grepping the bundle for icon content is +therefore not evidence the twins are in use; check the rendered DOM for a `viewBox` +attribute instead. + +Graded after the fix: AccessibilityControls and ThemeToggle both `match` across all +stories. + +## Source changes this work required + +Committed source, not just sync config: + +- `src/config/blockThemes.ts` — `createThemeDefinition` extracted from `config/blocks`, + which re-exports it so every existing import still works. Needed because importing the + factory from `config/blocks` registers every block and drags in QuoteBlock's slate graph. +- `src/storybook/{dsEntry,dsGlobals,dsConfig}.ts`, `src/storybook/DsPreviewProvider.tsx`. + +The 244-test suite passes with all of it. + +## Re-sync risks + +- **Nothing is anchored** — the project is pinned but no `_ds_sync.json` has been + uploaded, so a future run re-verifies everything. That is the documented safe state. +- **`config.json` holds absolute paths** (`storybookConfigDir`, `storybookStatic`, + `entry`) because the converter runs from a different cwd. They are machine-specific and + will need rewriting on any other checkout. +- The overlay and the emitted `.d.ts`/`theme.css` are all gitignored build products — + every one of them must be regenerated after a fresh clone or a `pnpm install`. +- Érico deleted his two earlier Claude Design projects before this run. The general + caution still stands: never sync into a project holding hand-authored work, because the + reconciliation pass deletes everything the build does not produce. diff --git a/.design-sync/build-css.sh b/.design-sync/build-css.sh new file mode 100755 index 0000000..e8f0fea --- /dev/null +++ b/.design-sync/build-css.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# Compile a standalone stylesheet for the Claude Design export. +# +# The add-on ships SCSS, not CSS: Volto's webpack compiles it and style-loader +# injects it at runtime, so the storybook build contains no .css file for the +# converter to scrape ([CSS_RUNTIME]). This produces one, from upstream VLT's +# theme plus ours, in the same order the app applies them. +# +# `addonsThemeCustomizationsVariables` is a virtual module Volto's build +# generates per project; sass cannot resolve it, so a stub satisfies the import. +set -euo pipefail + +R="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +OUT="$R/frontend/packages/volto-light-theme/dist/theme.css" +SASS="$R/frontend/core/packages/volto/node_modules/.bin/sass" +# Pin to the version the add-on declares — the pnpm store holds several, and a +# bare glob picks whichever sorts first (alpha.22 before alpha.31). +VLT_VERSION="$(node -p "require('$R/frontend/packages/volto-light-theme/package.json').dependencies['@kitconcept/volto-light-theme'].replace(/^[^0-9]*/,'')")" +UPSTREAM="$(ls -d "$R"/frontend/node_modules/.pnpm/@kitconcept+volto-light-theme@"$VLT_VERSION"_*/node_modules/@kitconcept/volto-light-theme | head -1)" +[ -d "$UPSTREAM" ] || { echo "upstream VLT $VLT_VERSION not found in the pnpm store" >&2; exit 1; } +OURS="$R/frontend/packages/volto-light-theme/src/theme" +STUBS="$R/.design-sync/.cache/scss-stubs" + +mkdir -p "$STUBS" "$(dirname "$OUT")" +: > "$STUBS/_addonsThemeCustomizationsVariables.scss" +: > "$STUBS/_addonsThemeCustomizationsMain.scss" + +ENTRY="$STUBS/ds-theme.scss" +cat > "$ENTRY" <<EOF +@import '$UPSTREAM/src/theme/main'; +@import '$OURS/main'; +EOF + +"$SASS" --no-source-map --quiet \ + --load-path="$STUBS" \ + --load-path="$UPSTREAM/src/theme" \ + --load-path="$OURS" \ + --load-path="$R/frontend/node_modules" \ + "$ENTRY" "$OUT" + +printf 'wrote %s (%s bytes)\n' "$OUT" "$(wc -c < "$OUT" | tr -d ' ')" diff --git a/.design-sync/build-icons.py b/.design-sync/build-icons.py new file mode 100644 index 0000000..08db1dc --- /dev/null +++ b/.design-sync/build-icons.py @@ -0,0 +1,88 @@ +"""Generate JS twins for Volto's SVG icons, for bundlers that aren't webpack. + +Volto's `Icon` component reads `name.attributes.viewBox` and `name.content` — the shape +`svg-loader` produces from an `.svg` import under webpack. The design-system converter +bundles with esbuild, whose loader map hardcodes `.svg` to `dataurl`, so `Icon` receives a +string, renders nothing, and its button collapses to an empty box. + +This writes a `<name>.svg.js` beside nothing at all — into a generated directory the +overlay points `@plone/volto/icons` at. esbuild falls back to `<path>.js` when the exact +file is absent, so `import clearSVG from '@plone/volto/icons/clear.svg'` resolves here. +The generated directory deliberately does NOT contain the `.svg` files themselves: if it +did, esbuild would find them first and apply the dataurl loader again. + +The parsing mirrors `svg-loader@0.0.2/index.js` statement for statement, so the previews +get exactly what the storybook renders. +""" + +from __future__ import annotations + +import json +import pathlib +import re +import shutil + +ROOT = pathlib.Path(__file__).resolve().parent.parent +# (source directory, generated directory). Volto's icons are reached as +# `@plone/volto/icons/*.svg`; ours as `@simplesconsultoria/volto-light-theme/icons/*.svg` +# — both are package-absolute, which is what lets the overlay reroute them. A relative +# `../../icons/x.svg` resolves straight to the real file and cannot be intercepted. +SOURCES = [ + ( + ROOT / "frontend/core/packages/volto/src/icons", + ROOT / ".design-sync/.cache/volto-icons", + ), + ( + ROOT / "frontend/packages/volto-light-theme/src/icons", + ROOT / ".design-sync/.cache/sc-icons", + ), +] + +SVG_RE = re.compile(r"<svg([^>]+)+>([\s\S]+)</svg>", re.I) +ATTR_RE = re.compile(r"""([\w\-:]+)(=)?("[^<>"]*"|'[^<>']*'|[\w\-:]+)""") + + +def parse(svg: str) -> dict: + """Extract the root attributes and inner markup, as `svg-loader` does. + + :param svg: the raw contents of an `.svg` file. + :returns: the ``{"attributes": ..., "content": ...}`` object `Icon` consumes. + """ + match = SVG_RE.search(svg) + if not match: + return {"attributes": {}, "content": ""} + + attributes: dict[str, object] = {} + for attr in ATTR_RE.findall(match.group(1) or ""): + name, _, value = attr + attributes[name] = value.strip("'\"") if value else True + + content = re.sub(r"\n", " ", match.group(2) or "").strip() + return {"attributes": attributes, "content": content} + + +def main() -> None: + for src, out in SOURCES: + assert src.is_dir(), f"icons not found at {src}" + if out.exists(): + shutil.rmtree(out) + out.mkdir(parents=True) + + written = 0 + empty = [] + for svg in sorted(src.glob("*.svg")): + payload = parse(svg.read_text(encoding="utf-8")) + if not payload["content"]: + empty.append(svg.name) + (out / f"{svg.name}.js").write_text( + f"export default {json.dumps(payload)};\n", encoding="utf-8" + ) + written += 1 + + print(f"wrote {written} icon modules to {out.relative_to(ROOT)}") + if empty: + print(f" ! parsed no content for: {', '.join(empty)}") + + +if __name__ == "__main__": + main() diff --git a/.design-sync/build-overlay.py b/.design-sync/build-overlay.py new file mode 100644 index 0000000..966da43 --- /dev/null +++ b/.design-sync/build-overlay.py @@ -0,0 +1,175 @@ +"""Build a synthetic node_modules that encodes Volto's webpack alias scheme. + +Volto's RelativeResolverPlugin rewrites `@plone/volto/X` to `@plone/volto/src/X` at +build time. esbuild can't do that, so the overlay points those two packages at their +`src/` directories and symlinks everything else through unchanged. +""" + +import os +import pathlib +import shutil + +ROOT = pathlib.Path(__file__).resolve().parent.parent +OVERLAY = ROOT / ".design-sync/nm-overlay" +SOURCES = [ + ROOT / "frontend/packages/volto-light-theme/node_modules", + ROOT / "frontend/node_modules", + # Volto's src imports its own deps (react-redux, semantic-ui-react, ...) + # from here, not from the app's node_modules. + ROOT / "frontend/core/packages/volto/node_modules", +] + +# package name -> the directory it should really resolve to +ALIASES = { + "@plone/volto": ROOT / "frontend/core/packages/volto/src", + "@plone/volto-slate": ROOT / "frontend/core/packages/volto-slate/src", +} +kit = list((ROOT / "frontend/node_modules/.pnpm").glob( + "@kitconcept+volto-light-theme@8.0.0-alpha.31*/node_modules/@kitconcept/volto-light-theme" +)) +assert kit, "kitconcept VLT alpha.31 not found in the pnpm store" +ALIASES["@kitconcept/volto-light-theme"] = kit[0] / "src" + +for target in ALIASES.values(): + assert target.is_dir(), f"alias target missing: {target}" + +if OVERLAY.exists(): + shutil.rmtree(OVERLAY) +OVERLAY.mkdir(parents=True) + +linked = 0 +for src in SOURCES: + if not src.is_dir(): + continue + for entry in sorted(src.iterdir()): + if entry.name.startswith(".") and entry.name != ".pnpm": + continue + if entry.name.startswith("@"): + scope = OVERLAY / entry.name + scope.mkdir(exist_ok=True) + for pkg in sorted(entry.iterdir()): + dest = scope / pkg.name + if not dest.exists(follow_symlinks=False): + dest.symlink_to(pkg.resolve()) + linked += 1 + else: + dest = OVERLAY / entry.name + if not dest.exists(follow_symlinks=False): + dest.symlink_to(entry.resolve()) + linked += 1 + +# apply the aliases last so they win over the plain symlinks above +for name, target in ALIASES.items(): + dest = OVERLAY / name + dest.parent.mkdir(parents=True, exist_ok=True) + if dest.exists(follow_symlinks=False) or dest.is_symlink(): + dest.unlink() + dest.symlink_to(target.resolve()) + +# @plone/volto becomes a real directory mirroring volto/src, so that `icons` can +# point at the generated JS twins instead of the .svg files. esbuild's loader map +# hardcodes .svg to dataurl, which makes Volto's Icon render nothing; the twins +# carry the {attributes, content} shape it actually reads. Run build-icons.py first. +ICONS = ROOT / ".design-sync/.cache/volto-icons" +if ICONS.is_dir(): + volto_src = ALIASES["@plone/volto"] + volto = OVERLAY / "@plone/volto" + if volto.is_symlink(): + volto.unlink() + elif volto.exists(): + shutil.rmtree(volto) + volto.mkdir(parents=True) + for child in sorted(volto_src.iterdir()): + if child.name == "icons": + continue + (volto / child.name).symlink_to(child.resolve()) + (volto / "icons").symlink_to(ICONS.resolve()) + print(f"@plone/volto/icons -> {len(list(ICONS.glob('*.js')))} generated icon modules") +else: + print("! .design-sync/.cache/volto-icons missing — run build-icons.py; icons will render empty") + +# The DS package itself gets a synthetic view rather than a plain symlink. The +# converter reads the component roster from the .d.ts the package's `types` +# field names, and the real package.json has none (it ships TS source, and its +# only export is `applyConfig`). Declaring `types` here points the converter at +# the curated design-system surface without touching the published manifest. +import json +PKG = "@simplesconsultoria/volto-light-theme" +PKG_REAL = ROOT / "frontend/packages/volto-light-theme" +view = OVERLAY / PKG +if view.is_symlink(): + view.unlink() +elif view.exists(): + shutil.rmtree(view) +view.mkdir(parents=True) +real_manifest = json.loads((PKG_REAL / "package.json").read_text()) +(view / "package.json").write_text(json.dumps({ + **real_manifest, + "types": "dist/src/storybook/dsEntry.d.ts", +}, indent=2) + "\n") +for child in ("src", "dist", "locales", "node_modules", "public"): + target = PKG_REAL / child + if target.exists(): + (view / child).symlink_to(target.resolve()) + +# Our own icons need the same JS twins as Volto's, for the same reason. Reached as +# `@simplesconsultoria/volto-light-theme/icons/<name>.svg`, which is why the two +# components using them import that form rather than a relative path. +SC_ICONS = ROOT / ".design-sync/.cache/sc-icons" +if SC_ICONS.is_dir(): + (view / "icons").symlink_to(SC_ICONS.resolve()) +else: + print("! .design-sync/.cache/sc-icons missing — run build-icons.py") +print(f"synthetic package view: {view.relative_to(ROOT)} (types -> dist/src/storybook/dsEntry.d.ts)") + +# @plone/registry is a module-local singleton (`const instance = new Config()`), +# so the converter's bundle and each compiled preview would each get their OWN +# empty config — seeding one is invisible to the other, and components that read +# `config.settings` throw in the preview however well the bundle is set up. This +# shim memoises the instance on globalThis so every copy shares one. +REG_REAL = ROOT / "frontend/core/packages/registry" +reg = OVERLAY / "@plone/registry" +if reg.is_symlink(): + reg.unlink() +elif reg.exists(): + shutil.rmtree(reg) +reg.mkdir(parents=True) +(reg / "package.json").write_text(json.dumps({ + "name": "@plone/registry", + "version": "0.0.0-ds-shim", + "main": "index.js", + "module": "index.js", + "types": f"{REG_REAL}/src/index.ts", +}, indent=2) + "\n") +(reg / "index.js").write_text( + f"import instance from '{REG_REAL}/src/index.ts';\n" + f"export * from '{REG_REAL}/src/index.ts';\n" + "const g = globalThis;\n" + "if (!g.__dsPloneRegistry) g.__dsPloneRegistry = instance;\n" + "export default g.__dsPloneRegistry;\n" +) +for child in ("addon-registry", "src"): + t = REG_REAL / child + if t.exists(): + (reg / child).symlink_to(t.resolve()) +print("shimmed @plone/registry as a globalThis singleton") + +print(f"overlay: {linked} packages linked, {len(ALIASES)} aliased") +for name in ALIASES: + probe = OVERLAY / name + # @plone/volto is a mirrored directory rather than a symlink (see the icons + # reroute above), so report the target only when there is one to read. + target = os.readlink(probe) if probe.is_symlink() else "(mirrored directory)" + print(f" {name} -> {target}") + +# prove the alias actually resolves the paths esbuild failed on +checks = [ + "@plone/volto/components/theme/Icon/Icon.jsx", + "@plone/volto/icons/clear.svg", + "@plone/volto/registry.js", + "@plone/volto/config.js", + "@kitconcept/volto-light-theme/primitives/Card/Card.jsx", +] +print("\nresolution probes:") +for rel in checks: + print(f" {'ok ' if (OVERLAY / rel).exists() else 'MISS'} {rel}") diff --git a/.design-sync/config.json b/.design-sync/config.json new file mode 100644 index 0000000..cb1526a --- /dev/null +++ b/.design-sync/config.json @@ -0,0 +1,60 @@ +{ + "pkg": "@simplesconsultoria/volto-light-theme", + "shape": "storybook", + "storybookConfigDir": "/Users/ericof/Projects/SimplesConsultoria/Code/sc-volto-light-theme/frontend/.storybook", + "storybookStatic": "/Users/ericof/Projects/SimplesConsultoria/Code/sc-volto-light-theme/.design-sync/sb-reference", + "entry": "/Users/ericof/Projects/SimplesConsultoria/Code/sc-volto-light-theme/frontend/packages/volto-light-theme/src/storybook/dsEntry.ts", + "srcDir": "frontend/packages/volto-light-theme/src", + "cssEntry": "dist/theme.css", + "provider": { + "component": "DsPreviewProvider" + }, + "projectId": "f0a26637-18ca-4f24-80e7-8d196207efcc", + "titleMap": { + "Grid": null, + "Highlight": null, + "Navigation": null, + "QuoteBlock": null, + "ThemeForm": null, + "ThemeSwatches": null, + "ThemesList": null, + "ThemesToolbarActions": null, + "ThemesUI": null, + "Gallery": null, + "Inventory": null, + "DefaultView": null, + "Separator": null + }, + "overrides": { + "HeroBlock": { + "cardMode": "column" + }, + "MainImageBlock": { + "cardMode": "column" + }, + "HeaderBarActions": { + "cardMode": "column" + }, + "MenuItem": { + "cardMode": "column" + }, + "SubMenu": { + "cardMode": "column" + }, + "SubMenuItem": { + "cardMode": "column" + }, + "SubMenuItems": { + "cardMode": "column" + }, + "SubMenuSection": { + "cardMode": "column" + } + }, + "tsconfig": "../../../.design-sync/tsconfig.ds.json", + "storyImports": { + "loaders": { + ".jpg": "dataurl" + } + } +} diff --git a/.design-sync/tsconfig.ds.json b/.design-sync/tsconfig.ds.json new file mode 100644 index 0000000..3af1bc1 --- /dev/null +++ b/.design-sync/tsconfig.ds.json @@ -0,0 +1,30 @@ +/* + tsconfig for the Claude Design build only — never for the app or the tests. + + The package's own tsconfig maps `@simplesconsultoria/volto-light-theme/*` to `./src/*`, + and esbuild honours that mapping ahead of the --node-modules overlay. Icon imports + therefore reached the real .svg files and were loaded as data URLs, which makes Volto's + Icon render an empty <svg>. Mapping the icons subpath at the generated JS twins puts the + {attributes, content} shape Icon reads back into the bundle. + + Two traps, both silent: + + - Write comments as a block like this one, NOT as a `"//"` key. The converter strips + comments with a regex that treats `"//":` as a line comment, mangling the JSON; it + then parses nothing and drops the paths mapping without saying so. + - The target must end in a bare `*`, never `*.js`. The plugin strips only a trailing + `*` and appends its own extension list, so `*.js` becomes a literal directory name, + the rule never matches, and the broader `/*` rule below silently wins — sending icon + imports back to the real .svg files. +*/ +{ + "extends": "../frontend/packages/volto-light-theme/tsconfig.json", + "compilerOptions": { + "paths": { + "@simplesconsultoria/volto-light-theme/icons/*": ["./.cache/sc-icons/*"], + "@simplesconsultoria/volto-light-theme/*": [ + "../frontend/packages/volto-light-theme/src/*" + ] + } + } +} diff --git a/.gitignore b/.gitignore index 9099385..984abff 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,23 @@ node_modules /core /public *.mo + +# design-sync (Claude Design) +# Ignore the working state; keep the durable inputs a re-sync needs. Everything +# under .design-sync/ that is NOT ignored here is the sync's repo footprint: +# sb-reference/, .cache/, learnings/ and nm-overlay/ are all regenerated. +.design-sync/* +!.design-sync/config.json +!.design-sync/NOTES.md +!.design-sync/conventions.md +!.design-sync/tsconfig.ds.json +!.design-sync/*.py +!.design-sync/*.sh +!.design-sync/previews/ +!.design-sync/overrides/ + +# Staged converter scripts, generated bundle, and build products of the sync. +.ds-sync/ +ds-bundle/ +frontend/packages/volto-light-theme/dist/ +frontend/packages/volto-light-theme/index.d.ts diff --git a/DESIGN_SYSTEM.md b/DESIGN_SYSTEM.md new file mode 100644 index 0000000..73e6391 --- /dev/null +++ b/DESIGN_SYSTEM.md @@ -0,0 +1,14 @@ +# Design System + +The design system for `@simplesconsultoria/volto-light-theme` is documented under +[`docs/design-system/`](./docs/design-system/). + +| document | contents | +|---|---| +| [DESIGN_SYSTEM.md](./docs/design-system/DESIGN_SYSTEM.md) | the manifest — what the add-on owns, what it inherits, and the rules that govern how they compose | +| [INVENTORY.md](./docs/design-system/INVENTORY.md) | exhaustive tables: block schemas, listing variations, all custom properties with resolved light/dark values, the type scale | +| [GAPS.md](./docs/design-system/GAPS.md) | verified defects, a WCAG contrast audit of the shipped palette, and the open design decisions | +| [previews/](./docs/design-system/previews/) | self-contained preview pages, marked up for import into Claude Design | + +The most current answer to "what blocks exist" is not in any of these — it is the +`Public/Blocks/Inventory` story, which reads the block registry at runtime. diff --git a/backend/news/+content-colors.feature b/backend/news/+content-colors.feature new file mode 100644 index 0000000..9a0b460 --- /dev/null +++ b/backend/news/+content-colors.feature @@ -0,0 +1 @@ +Added `event_color`, `file_color` and `image_color` (with `_light` and `_dark` variants) to the `ISCVLTThemeDefinition` schema and updated `default` and `initial` registry profiles, allowing site themes to control content-type colors directly from the themes control panel. @humanaice diff --git a/backend/src/sc/voltolighttheme/interfaces.py b/backend/src/sc/voltolighttheme/interfaces.py index cb8b3f6..04c7145 100644 --- a/backend/src/sc/voltolighttheme/interfaces.py +++ b/backend/src/sc/voltolighttheme/interfaces.py @@ -84,6 +84,18 @@ class ISCVLTThemeDefinition(model.Schema): "neutral_accent_color_dark", ], ) + model.fieldset( + "content_types", + label=_("Content Types Colors"), + fields=[ + "event_color_light", + "event_color_dark", + "file_color_light", + "file_color_dark", + "image_color_light", + "image_color_dark", + ], + ) name = schema.TextLine( title="Theme name", @@ -318,3 +330,27 @@ class ISCVLTThemeDefinition(model.Schema): ), required=False, ) + event_color_light = fields.Color( + title=_("label_event_color_light", default="Event Color (Light)"), + required=False, + ) + event_color_dark = fields.Color( + title=_("label_event_color_dark", default="Event Color (Dark)"), + required=False, + ) + file_color_light = fields.Color( + title=_("label_file_color_light", default="File Color (Light)"), + required=False, + ) + file_color_dark = fields.Color( + title=_("label_file_color_dark", default="File Color (Dark)"), + required=False, + ) + image_color_light = fields.Color( + title=_("label_image_color_light", default="Image Color (Light)"), + required=False, + ) + image_color_dark = fields.Color( + title=_("label_image_color_dark", default="Image Color (Dark)"), + required=False, + ) diff --git a/backend/src/sc/voltolighttheme/profiles/default/registry/sc.voltolighttheme.themes.xml b/backend/src/sc/voltolighttheme/profiles/default/registry/sc.voltolighttheme.themes.xml index 622a8a9..ab08902 100644 --- a/backend/src/sc/voltolighttheme/profiles/default/registry/sc.voltolighttheme.themes.xml +++ b/backend/src/sc/voltolighttheme/profiles/default/registry/sc.voltolighttheme.themes.xml @@ -52,6 +52,12 @@ <value key="neutral_low_foreground_color_dark">#e6e6e6</value> <value key="neutral_accent_color_light">#f4822c</value> <value key="neutral_accent_color_dark">#f4822c</value> + <value key="event_color_light">#d32f2f</value> + <value key="event_color_dark">#d32f2f</value> + <value key="file_color_light">#1976d2</value> + <value key="file_color_dark">#1976d2</value> + <value key="image_color_light">#388e3c</value> + <value key="image_color_dark">#388e3c</value> </records> </registry> diff --git a/backend/src/sc/voltolighttheme/profiles/initial/registry/sc.voltolighttheme.themes.xml b/backend/src/sc/voltolighttheme/profiles/initial/registry/sc.voltolighttheme.themes.xml index 10abdef..f1ce7cd 100644 --- a/backend/src/sc/voltolighttheme/profiles/initial/registry/sc.voltolighttheme.themes.xml +++ b/backend/src/sc/voltolighttheme/profiles/initial/registry/sc.voltolighttheme.themes.xml @@ -46,6 +46,12 @@ <value key="neutral_low_foreground_color_dark">#9e9e9e</value> <value key="neutral_accent_color_light">#d32f2f</value> <value key="neutral_accent_color_dark">#d32f2f</value> + <value key="event_color_light">#d32f2f</value> + <value key="event_color_dark">#d32f2f</value> + <value key="file_color_light">#1976d2</value> + <value key="file_color_dark">#1976d2</value> + <value key="image_color_light">#388e3c</value> + <value key="image_color_dark">#388e3c</value> </records> </registry> diff --git a/docs/design-system/DESIGN_SYSTEM.md b/docs/design-system/DESIGN_SYSTEM.md new file mode 100644 index 0000000..b0055e2 --- /dev/null +++ b/docs/design-system/DESIGN_SYSTEM.md @@ -0,0 +1,317 @@ +# Design System — `@simplesconsultoria/volto-light-theme` + +The manifest for this add-on's design system: what it owns, what it inherits, and the +rules that govern how the two compose. + +Every factual claim here was checked against the source or measured in a browser on +2026-09-01/02. Where something is a decision nobody has taken yet, it says so rather than +guessing. + +- **Package:** `@simplesconsultoria/volto-light-theme` +- **Built on:** `@kitconcept/volto-light-theme@8.0.0-alpha.31` (the `theme` in `package.json`) +- **Companion documents:** [INVENTORY.md](./INVENTORY.md) for the exhaustive tables, + [GAPS.md](./GAPS.md) for defects and open questions + +--- + +## 1. How to read this system + +Three layers stack, and almost everything confusing about the theme comes from not +knowing which layer you are looking at: + +| layer | supplies | you change it by | +|---|---|---| +| **Volto core** + `@plone/volto-slate` | the block vocabulary and page rendering | not at all | +| **`@kitconcept/volto-light-theme`** | most of the visual language, the `--theme-*` contract, `@property` registrations, `initialBlocks` per type | upgrading the dependency | +| **this add-on** | 4 blocks, 3 listing variations, 2 template overrides, the block theme factory, the semantic palette, the type scale | editing this package | +| **a project using it** | concrete colours, via the Themes control panel | an `ISCVLTThemeDefinition` record | + +**The single most useful entry point is Storybook**, not this file. `Public/Blocks/Inventory` +reads `config.blocks.blocksConfig` at runtime, so it always lists what an editor actually +has — 42 blocks today. This document explains the parts that a table cannot. + +--- + +## 2. What this add-on owns + +### 2.1 Blocks + +Four, registered in `src/config/blocks.ts`: + +| id | title | group | notes | +|---|---|---|---| +| `heroBlock` | Hero Block | common | two variations (`flex`, `card`); dynamic schema; 30 `--theme-*` references — the most theme-sensitive block in the system | +| `mainImageBlock` | Main Image | media | renders the content item's `preview_image_link`; **no colour tokens at all**, only container widths | +| `quoteBlock` | Quote | text | Slate body; the only own block reading semantic tokens (`--accent-color`) directly as well as `--theme-*` | +| `documentByline` | Byline | common | publication/modification dates and authors; **ships no stylesheet** — see GAPS §2.1 | + +Full schemas in [INVENTORY.md](./INVENTORY.md) §1. + +### 2.2 Listing variations + +Three added, two overridden — `src/config/blocks.ts` → `install()`: + +| id | title | action | +|---|---|---| +| `carousel` | Carousel | added | +| `mediaCarousel` | Media Carousel | added | +| `teaser` | Highlight | added | +| `grid` | *(upstream)* | template replaced with `GridTemplate` | +| `imageGallery` | *(upstream)* | template replaced with `GridTemplate` | + +Each addition is guarded by an existence check, so `install()` is idempotent, and the +`listing` block's own `schemaEnhancer` is *composed with* rather than replacing upstream's. + +### 2.3 The `gridBlock` extension + +`installGridBlock()` assigns this add-on's themes to both `gridBlock.themes` and +`gridBlock.blocksConfig.themes`, and appends eight ids to `allowedBlocks`: +`documentByline`, `__button`, `listing`, `slider`, `carousel`, `mainImageBlock`, +`heroBlock`, `quoteBlock`. Core caps the grid at `maxLength: 4`. + +--- + +## 3. The block theme system + +This is the part most worth understanding, because it is this add-on's own invention and +it is what a project extends. + +`createThemeDefinition(name, label)` maps **14 semantic `--theme-*` properties** onto +`--block-theme-{name}-*` tokens, in four groups: + +- **Ground** — the block's own background and text: `bg`, `text`, `high-contrast`, + `low-contrast`, `accent-color` +- **High Ground** — elevated elements inside it (cards, chips): `high-bg`, `top-text`, + `top-high-contrast`, `top-low-contrast`, `top-accent-color` +- **Border** — `border`, `border-width` +- **Pattern** — `pattern-image`, `pattern-opacity`. Declared, defined as `none`/`0`, and + **consumed nowhere**. Implement or delete; see GAPS §1.2. + +Two themes ship: `default` (labelled "Primary") and `brand` ("Brand"). Adding a third +downstream is one line plus a matching token block in `_root.scss`: + +```ts +createThemeDefinition('purple', 'Purple') +``` + +> **The contract is enforced by a test.** `src/theme/blockThemes.test.ts` fails if the +> factory maps a property `_root.scss` does not declare in its `:root` block, or if a +> stylesheet reads a `--theme-*` no theme supplies. It exists because the failure is +> otherwise silent — a `var()` with no definition and no fallback is invalid at +> computed-value time, so the whole declaration is dropped. That is how six focus +> outlines went missing. + +--- + +## 4. Colour + +### 4.1 Structure + +Ten base values feed a **4 × 4 semantic matrix** — `primary` / `secondary` / `accent` / +`neutral`, each with `color`, `foreground-color`, `low-foreground-color`, +`accent-color`. Every semantic token is a `light-dark()` pair, so the system is +dual-mode by construction. + +Resolved values for both modes: [INVENTORY.md](./INVENTORY.md) §5.2. Visual: +[previews/colors.html](./previews/colors.html). + +### 4.2 The rule that matters + +**Components read semantic tokens, never base values.** `--gray-80` appears in +`_root.scss` and essentially nowhere else; a component that reaches for it bypasses both +colour modes and the Themes control panel. + +### 4.3 Registered properties — a real constraint + +Upstream registers six tokens with `@property { syntax: '<color>' }`: `--primary-color`, +`--primary-foreground-color`, `--secondary-color`, `--secondary-foreground-color`, +`--accent-color`, `--accent-foreground-color`. + +A **registered** custom property is computed eagerly at the element that declares it, so +its `light-dark()` resolves once at `:root` and inherits as a flat colour. An +**unregistered** one stays an unresolved token stream and resolves wherever it is *used*, +following the `color-scheme` in force at that element. + +The two halves therefore fail in opposite directions, which is what makes this confusing +in practice: + +| | mode set on `:root` only | mode set on a wrapper only | +|---|---|---| +| the six **registered** tokens | correct | frozen at the root's mode | +| every **unregistered** token | frozen, *if something resets `color-scheme` below the root* | correct | + +Storybook's preview does reset `color-scheme` below `<html>`, so neither placement alone +works there — `withTheme` sets it in **both** places. On a real page the mode lives on +`<html>` and the question never arises; it only bites when switching mode on a descendant. + +### 4.4 Contrast + +Both AA failures the first audit found are now fixed. They were the two accent tokens +that used `--brand-color` in *both* modes rather than swapping between the brand shades +the way `--primary-accent-color` and `--accent-color` do: + +| token | mode | was | now | +|---|---|---|---| +| `--secondary-accent-color` | dark, on `#ffffff` | `#f4822c` — 2.60 **FAIL** | `#b55e1c` — 4.58 AA | +| `--neutral-accent-color` | light, on `#edeff0` | `#f4822c` — 2.25 **FAIL** | `#af5009` — 4.58 AA | + +The second one is why `--brand-color-darker` (`#af5009`) exists: `#b55e1c` reaches only +3.97 against `#edeff0`, which is AA-large at best. + +> Every ratio in this system is a **default**. A site that sets its own colours through +> the Themes control panel replaces them, so the audit has to be re-run per project. + +--- + +## 5. Typography + +Eleven steps in `$font-sizes`, and now eleven in `$line-heights` too. Declared in +**rem** so the global `--font-scale` on `html` resizes text emitted by upstream mixins. + +The six scalar overrides (`$font-size`, `$heading1`, …) are exact px→rem conversions of +upstream's `!default` values. That conversion is their entire purpose; they are not +duplicates and should stay. + +All four font tokens resolve to `system-ui` — the theme ships no brand typeface. Note +that `--custom-main-font` has no consumer in this package but drives upstream's +`$page-font`. + +Scale and heading rules: [previews/typography.html](./previews/typography.html). + +--- + +## 6. Layout + +| token | value | +|---|---| +| `--narrow-container-width` | `940px !important` | +| `--default-container-width` | `940px !important` | +| `--layout-container-width` | `1440px !important` | + +`narrow` and `default` are currently identical, so two of the four `blockWidth` choices +are indistinguishable; all three carry `!important`, which blocks downstream override. +Both are open questions — GAPS §3.1. + +--- + +## 7. Scoping rules — the thing that trips people up + +Stylesheets in this package are scoped to ancestors that only exist on a real page. A +component rendered outside its chain gets **none** of its styling, and the symptom always +looks like missing CSS rather than a missing wrapper: + +| stylesheet | scoped to | +|---|---| +| `_listing.scss` | `body .block.listing`, then `&.carousel` / `&.mediaCarousel` / … | +| `_header.scss`, `_headerBar.scss`, `_mobileTools.scss` | `body header.header-wrapper` | +| `_navigation.scss` | `#navigation.navigation` **and** `&.scNavigation` | +| any themed block | needs `--theme-*` from a themed wrapper | +| `_accessibilityControls.scss`, `_themeToggle.scss` | *unscoped* — but drawn for the dark bar, so they need the right **ground**, not the right ancestor | + +Anything rendering these outside a page — a story, a preview, a screenshot harness — has +to reproduce the chain. + +The accessibility controls are the instructive counter-example: their selectors are +unscoped, so the rules *did* apply, and they still looked wrong — the buttons are drawn as +light pills for the dark `--secondary-color` bar, so on a pale ground they read as +unstyled browser defaults. The CSS was never the problem; the **ground** was. + +The rule this suggests: a story should reproduce the ancestor chain its component's +stylesheet names, and sit on a themed ground rather than a hardcoded grey — the hardcoded +value is exactly what hides the mismatch. + +--- + +## 8. Per-project overlay + +A project supplies colours through the Themes control panel, not by forking this package. +The backend serves an `ISCVLTThemeDefinition` with **38 colour fields** — the four +semantic families × four roles, plus the three content types — each of them a `_light` / +`_dark` pair. `helpers/themeStyles.ts` maps every field onto the custom property of the +same name, so `primary_color_light` becomes `--primary-color-light`: exactly the override +hook `_root.scss` reads inside its `light-dark()`. A themed site therefore keeps both +colour modes. + +The mapping is mechanical, so a colour added to the schema needs no frontend change — and +`themeStyles.test.ts` now checks **both** ends of that contract: that the pinned field +list still matches the schema, and that `_root.scss` actually reads every property the +mapping derives. The second half is the one that was missing when the content-type +colours were wired to `--event-color-override`, a property nothing writes. + +`--header-foreground-color` is the exception in the other direction: `_root.scss` reads +it, but no field of `ISCVLTThemeDefinition` supplies it, so nothing can set it today. + +`Themes/fixtures.ts` holds three realistic records, and the Storybook toolbar can apply +any of them to any story. + +--- + +## 9. Storybook + +103 stories. The ones that answer design-system questions: + +| story | answers | +|---|---| +| `Public/Blocks/Inventory` | what blocks exist, and which layer supplies each | +| `Public/Blocks/Gallery` | every registered block rendered — all 42 without error | +| `Public/Views/Default View` | how blocks compose on a Document, News Item and Event | +| `Public/Blocks/Grid` | `gridBlock` at 1–4 columns | +| `Public/Blocks/Listing/*` | the four listing variations | +| `Public/Blocks/Separator` | block width × short line × alignment | +| `Public/Navigation/*` | the navigation bar and the fat menu | + +The toolbar carries three globals — **colour mode** (light / dark / high contrast), +**block theme** (Primary / Brand) and **site theme** — so any story can be checked in any +combination. See `src/storybook/withTheme.tsx`. + +Every story is constrained to `--default-container-width` and centred, so components are +seen at the measure they occupy on a page. Stories that genuinely span the viewport — +the page views, the header bar, the navigation — opt out with +`parameters: { fullBleed: true }`. + +### 9.1 Where a story lives + +A story for a component this package ships sits **beside the component**; a story for a +component from a dependency sits in **`src/storybook/`**, alongside the decorator and the +shared fixtures. That is why `gridBlock` and `separator` — core and +`@kitconcept/volto-separator-block` respectively — are not filed under +`components/Blocks/`. See `src/storybook/README.md`. + +### 9.2 Story images + +`frontend/.storybook/main.js` serves `packages/volto-light-theme/public` at the Storybook +root, so a file at `public/images/example.jpg` is reachable as `/images/example.jpg` and +ships with the package. Fixtures currently point at `picsum.photos` instead, which serves +arbitrary third-party photographs and needs network access — replacing them is the +provenance question in §11. `public/images/README.md` lists the four places a fixture +names its image host. + +--- + +## 10. Export to Claude Design + +Preview pages live in [previews/](./previews/), each marked with a first-line +`<!-- @dsCard group="…" -->` comment, which is what the Design System pane builds its card +index from. Foundations (`colors`, `typography`, `block-themes`) are generated from the +SCSS; the block previews are captured from the built Storybook with computed styles +inlined, so they are the real component output rather than a reimplementation. + +`register_assets` is legacy and not needed — the `@dsCard` markers are sufficient. + +--- + +## 11. Open decisions + +Nothing below is a defect; each is a choice nobody has made. Full context in +[GAPS.md](./GAPS.md) §6. + +- Is `--brand-color: #f4822c` a deliberate default, or inherited from a client site? + It feeds `--accent-color`, all four link tokens and both block themes' accent slots. +- Should the content-type colours (`#d32f2f`, `#1976d2`, `#388e3c`) be brand-aligned? + They sit off the brand ramp and carry the same value in both colour modes. +- Should `narrow` and `default` container widths differ? +- Should the `documentByline` block get a stylesheet? +- Implement the Pattern layer, or delete it? +- Translate `es` and `de`? Both are registered locales with empty catalogues; `pt_BR` is + complete at 147 messages. +- Replace the placeholder imagery and sample copy with assets of known provenance. diff --git a/docs/design-system/GAPS.md b/docs/design-system/GAPS.md new file mode 100644 index 0000000..b9113d2 --- /dev/null +++ b/docs/design-system/GAPS.md @@ -0,0 +1,118 @@ +# Design System Gaps + +What is still **open** in `@simplesconsultoria/volto-light-theme` — defects not yet fixed, +coverage not yet built, and decisions nobody has taken. + +Resolved items are not kept here. This document should answer "what is wrong now", and a +list of past fixes gets in the way of that; the history is in the changelog and in git. + +Every entry was verified against source or measured in a browser. Where the fix is a +judgment call rather than a correction, it says so — those are collected in §6. + +Companions: [DESIGN_SYSTEM.md](./DESIGN_SYSTEM.md) for how the system works, +[INVENTORY.md](./INVENTORY.md) for the exhaustive tables. + +--- + +## 1. Colour tokens + +### 1.1 `--theme-font-color` is consumed upstream but never mapped + +Read once by `@kitconcept/volto-light-theme@8.0.0-alpha.31`, not mapped by +`createThemeDefinition` and not defined here, so upstream's own fallback governs. Worth +confirming against upstream's intent before adding a mapping. + +### 1.2 The Pattern layer is declared but unimplemented + +`createThemeDefinition` maps `--theme-pattern-image` and `--theme-pattern-opacity`, and +`_root.scss` defines them as `none` / `0` for both themes so the mapping resolves — but +**nothing consumes them**. Implement the layer or delete the two mappings. + +### 1.3 Duplicated link tokens + +`--link-color` / `--color-link` and `--link-color-hover` / `--color-link-hover` are +duplicate pairs carrying the same values, kept for two naming conventions. Harmless, but a +project overriding one and not the other gets an inconsistent result. + +--- + +## 2. Components + +### 2.1 `documentByline` ships no stylesheet + +There is no `_documentByLine.scss` anywhere in `src/theme/`. In isolation this is +invisible, because a story gives the block a padded container; on a page it is obvious — +every other block sits in the centred content column while the byline starts flush against +the viewport edge. + +Its markup is three `Container` elements from `@plone/components` inside a bare +`<div class="documentByLine">`, and nothing constrains that wrapper to a container width. +Visible in the `Public/Views/Default View` stories. + +--- + +## 3. Layout + +### 3.1 `narrow` and `default` container widths are identical + +Both are `940px !important`, while `--layout-container-width` is `1440px`. The +`blockWidth` widget offers `narrow`, `default`, `layout` and `full` as distinct choices, +but two produce the same result. All three also carry `!important`, which blocks a +downstream override without another `!important`. + +--- + +## 4. Storybook coverage + +All 42 registered blocks render without error. Eleven still show an **empty state** for +want of sample data: + +| block | what it needs | +|---|---| +| `banner`, `carousel`, `heading`, `introduction`, `logos`, `maps`, `slider` | block data; derivable from each package's schema, but none of them ship a story or mocks to harvest | +| `toc` | sibling heading blocks — a composition fixture, not block data | +| `separator` | nothing; a horizontal rule legitimately has no text, and its five dedicated stories confirm it renders | +| `followUsBlock` | has data and does not error, but was never confirmed to paint visible links | + +### 4.1 Not verified + +- The block preview pages under `previews/` were generated from a Storybook capture whose + pipeline lives **outside the repo**, so nobody else can regenerate them. Either commit + the generator or drop them — shipping unreproducible generated artifacts is the worst of + the options. +- Story imagery is now fourteen bundled photographs in `src/storybook/images/`, imported + by the fixtures so they inline at build time and need no network. `.npmignore` keeps + them out of the published package. + +--- + +## 5. Translation coverage + +`pnpm i18n` extracts **147 messages** (the `.pot` reports 148 including its header). + +| locale | translated | note | +|---|---|---| +| `en` | 0 / 147 | intentional — an empty `msgstr` falls back to the English `defaultMessage` | +| `pt_BR` | 147 / 147 | complete; validated with `msgfmt --check` | +| `es` | 0 / 147 | registered locale, never translated | +| `de` | 0 / 147 | registered locale, never translated | + +`utils/speechSynthesis.ts` also hardcodes `pt-BR` when choosing a screen-reader voice. +Unlike the date formatters, that is arguably deliberate, so it was left alone. + +--- + +## 6. Open decisions — need a human + +Nothing here is a defect; each is a choice nobody has made. + +| # | question | why it matters | +|---|---|---| +| — | Is `--brand-color: #f4822c` a deliberate default, or inherited from a client site? | It feeds `--accent-color`, all four link tokens and both block themes' accent slots. It no longer gates a contrast failure — those are fixed — but it still decides what the theme looks like before a project touches it. | +| — | Should the content-type colours be brand-aligned? | `#d32f2f`, `#1976d2` and `#388e3c` are off the brand ramp and carry the same value in both colour modes. They resolve correctly and a site theme can now override them, so this is taste, not a defect. | +| — | Should `narrow` and `default` container widths differ (§3.1)? | Two of the four `blockWidth` choices are indistinguishable. | +| — | Implement or delete the Pattern layer (§1.2)? | Its tokens resolve but nothing consumes them. | +| — | Should `documentByline` get a stylesheet, and at what width (§2.1)? | It renders flush to the viewport edge on a page. | +| H14 | Translate `es` and `de` (§5)? | Both are registered locales with empty catalogues. | +| — | Replace placeholder imagery and sample copy with assets of known provenance (§4.1)? | Affects all 42 blocks, not only the ones with fixtures. | +| — | Is the Claude Design export still a goal? | Decides whether `previews/` earns its keep — see §4.1. | diff --git a/docs/design-system/INVENTORY.md b/docs/design-system/INVENTORY.md new file mode 100644 index 0000000..82d190b --- /dev/null +++ b/docs/design-system/INVENTORY.md @@ -0,0 +1,444 @@ +# Design System Inventory + +Machine-extracted inventory of `@simplesconsultoria/volto-light-theme`. + +Every row is derived from source by reading, not by inference. Evidence paths are +relative to `frontend/packages/volto-light-theme/`. `[V 2026-09-01]` marks a row +verified against the working tree on that date. + +- **Package:** `@simplesconsultoria/volto-light-theme` +- **Upstream:** `@kitconcept/volto-light-theme@8.0.0-alpha.31` (declared in `package.json`, `theme` field) +- **Scope:** this document covers the add-on's *own* vocabulary. Upstream VLT and Volto + core blocks are a referenced vocabulary and are not re-documented here. + +--- + +## 1. Blocks the add-on ships + +Registered in `src/config/blocks.ts` → `installLocalBlocks()`. `[V 2026-09-01]` + +| id | title (registration) | group | icon | sidebarTab | restricted | evidence | +|---|---|---|---|---|---|---| +| `documentByline` | `Byline` | `common` | `pencil.svg` | 1 | `false` (`mostUsed: true`) | `src/components/Blocks/DocumentByLine/index.ts` | +| `mainImageBlock` | `Main Image` | `media` | `image.svg` | 1 | only when the content item has `preview_image_link` | `src/components/Blocks/MainImageBlock/index.ts` | +| `heroBlock` | `Hero Block` | `common` | `presentation.svg` | 1 | `false` | `src/components/Blocks/HeroBlock/index.ts` | +| `quoteBlock` | `Quote` | `text` | `quote.svg` | 1 | `false` | `src/components/Blocks/QuoteBlock/index.ts` | + +All titles are English strings translated through `formatMessageWithFallback`, which uses +the string itself as the message id. See GAPS §5. + +### 1.1 `documentByline` + +Schema: `src/components/Blocks/DocumentByLine/schema.ts` + +| field | type | default | required | +|---|---|---|---| +| `showPublished` | `boolean` | `true` | no | +| `showModified` | `boolean` | `true` | no | +| `showAuthor` | `boolean` | `false` | no | + +Single fieldset `default`, `required: []`. **No** styling schema — this is the only own +block that does not call `defaultStylingSchema`, so it has no `theme` control. + +### 1.2 `mainImageBlock` + +Schema: `src/components/Blocks/MainImageBlock/schema.tsx` + +| field | widget / type | default | notes | +|---|---|---|---| +| `title` | `string` | — | | +| `description` | `string`, `textarea` | — | | +| `altText` | `string` | — | description links to the W3C alt-text decision tree | +| `align` | `align` | `center` | actions restricted to `left` / `right` / `center` | +| `size` | `image_size` | `l` | set to `m` in `properties`, then overridden to `l`; disabled when `align === 'center'` | +| `href` | `object_browser`, `mode: link` | — | fieldset `link_settings`; externals allowed | +| `openLinkInNewTab` | `boolean` | — | fieldset `link_settings` | +| `styles.blockWidth:noprefix` | `blockWidth` | `default` | `filterActions: narrow, default, layout, full`; disabled when `align` is `left`/`right` | +| `styles.theme` | `color_picker` | `default` | injected by `defaultStylingSchema` | + +Fieldsets: `default`, `styling`, `link_settings`. `required: []`. + +### 1.3 `heroBlock` + +Schema: `src/components/Blocks/HeroBlock/schema.ts`. Has a `dataAdapter` +(`adapter.ts`) and two variations declared in `index.ts`: + +| variation | title | default | +|---|---|---| +| `flex` | `Flex / Decorative` | yes | +| `card` | `Attached Card` | no | + +The schema is **dynamic** — fieldset composition depends on `formData`: + +- `default` fieldset always carries `href`, `overwrite`, `headerText`, `showDate`, `footerText` +- `+ title`, `description` only when `overwrite` is `true` +- `+ tags` only when `variation === 'flex'` +- `fileType` prepended only when `variation === 'card'` +- `image` fieldset carries `preview_image` only when `overwrite` is `true` + +| field | widget / type | default | choices | +|---|---|---|---| +| `href` | `object_browser`, `mode: link` | — | externals **not** allowed; **the only required field** | +| `overwrite` | `boolean` | `false` | | +| `headerText` | `string` | — | | +| `showDate` | `boolean` | `true` | | +| `title` | `string` | — | | +| `description` | `string`, `textarea` | — | | +| `footerText` | `string` | — | | +| `tags` | `array` of `string` | — | | +| `fileType` | `string` | — | free text; hint suggests `PDF`, `Report`, `Article` | +| `preview_image` | `object_browser`, `mode: image` | — | | +| `hideImage` | `boolean` | `false` | | +| `imageFit` | `string` | `cover` | `cover`, `contain` | +| `button` | `boolean` | — | | +| `buttonLink` | `object_browser`, `mode: link` | — | | +| `buttonText` | `string` | — | | +| `blockWidth` | `blockWidth` | `layout` | | +| `imageSize` | `string` | `50%` | `0%`, `30%`, `40%`, `50%`, `60%`, `70%` | +| `titleTag` | `string` | `h2` | `h1`, `h2`, `h3`, `p` | +| `textSide` | `align` | `left` | actions `left`, `right` | +| `fullWidth` | `boolean` | `false` | in the `layout` fieldset | +| `styles.theme` | `color_picker` | `default` | injected by `defaultStylingSchema` | + +`href` selects a content item and pulls `Title`, `Description`, `hasPreviewImage`, +`head_title`, `image_field`, `image_scales`, `@type`, `EffectiveDate`, `CreationDate`, +`effective`, `start`, `end`. + +### 1.4 `quoteBlock` + +Schema: `src/components/Blocks/QuoteBlock/schema.ts`. `blockHasOwnFocusManagement: true` +(the block body is a Slate editor). + +| field | type | default | choices | +|---|---|---|---| +| `value` | Slate value | — | not declared in the schema; supplied by the editor | +| `author` | `string` | — | | +| `backgroundStyle` | `string` | `transparent` | `transparent`, `filled` | +| `styles.theme` | `color_picker` | `default` | injected by `defaultStylingSchema` | + +`required: []`. + +--- + +## 2. Listing variations + +Installed in `src/config/blocks.ts` → `install()`. `[V 2026-09-01]` + +| id | title | template | schemaEnhancer | action | +|---|---|---|---|---| +| `grid` | *(upstream)* | `GridTemplate` | *(upstream)* | **overridden** | +| `imageGallery` | *(upstream)* | `GridTemplate` | *(upstream)* | **overridden** | +| `carousel` | `Carousel` | `CarouselTemplate` | `carouselSchemaEnhancer` | **added** | +| `mediaCarousel` | `Media Carousel` | `MediaCarouselTemplate` | `mediaCarouselSchemaEnhancer` | **added** | +| `teaser` | `Highlight` | `TeaserTemplate` | `teaserSchemaEnhancer` | **added** | + +All three additions are guarded by an `id` existence check, so re-running `install()` is +idempotent. The `listing` block's own `schemaEnhancer` is wrapped so +`listingSchemaEnhancer` composes with, rather than replaces, the upstream one. + +Variation titles go through `formatMessageWithFallback` like block titles, and are +registered for extraction in `src/index.ts`. + +## 3. `gridBlock` extension + +`installGridBlock()` — runs only if `gridBlock` is already registered. `[V 2026-09-01]` + +- Assigns `customThemes` to both `gridBlock.themes` and `gridBlock.blocksConfig.themes` +- Appends 8 ids to `allowedBlocks`: `documentByline`, `__button`, `listing`, `slider`, + `carousel`, `mainImageBlock`, `heroBlock`, `quoteBlock` +- Copies each of those blocks' config into `gridBlock.blocksConfig` + +--- + +## 4. The block theme system + +`createThemeDefinition(name, label)` in `src/config/blocks.ts:72`. `[V 2026-09-01]` + +The factory maps 14 semantic `--theme-*` custom properties onto +`--block-theme-{name}-*` tokens, in four groups: + +| group | `--theme-*` property | `--block-theme-{name}-*` suffix | +|---|---|---| +| Ground | `--theme-color` | `bg` | +| Ground | `--theme-foreground-color` | `text` | +| Ground | `--theme-high-contrast-foreground-color` | `high-contrast` | +| Ground | `--theme-low-contrast-foreground-color` | `low-contrast` | +| Ground | `--theme-foreground-accent-color` | `accent-color` | +| High Ground | `--theme-high-contrast-color` | `high-bg` | +| High Ground | `--theme-top-foreground-color` | `top-text` | +| High Ground | `--theme-top-high-contrast-foreground-color` | `top-high-contrast` | +| High Ground | `--theme-top-low-contrast-foreground-color` | `top-low-contrast` | +| High Ground | `--theme-top-accent-color` | `top-accent-color` | +| Border | `--theme-border-color` | `border` | +| Border | `--theme-border-width` | `border-width` | +| Pattern | `--theme-pattern-image` | `pattern-image` | +| Pattern | `--theme-pattern-opacity` | `pattern-opacity` | + +Two themes ship, both via the factory: + +```ts +const customThemes: ThemeDefinition[] = [ + createThemeDefinition('default', 'Primary'), + createThemeDefinition('brand', 'Brand'), +]; +``` + +Adding a third in a downstream project is a one-liner plus a matching +`--block-theme-{name}-*` block in `_root.scss`. + +All 14 suffixes are defined in `_root.scss` for both themes as of 2026-09-01. Four of +them previously were not, which silently dropped 12 declarations — including six focus +outlines. The mapping is now enforced by `src/theme/blockThemes.test.ts`. + +### 4.1 Which `--theme-*` properties are consumed + +Counted with `grep -rhoE 'var\(--theme-[a-z-]+'`. `[V 2026-09-01]` + +| property | upstream VLT | this add-on | resolves? | +|---|---|---|---| +| `--theme-foreground-color` | 121 | 18 | yes | +| `--theme-color` | 32 | 13 | yes | +| `--theme-high-contrast-color` | 20 | 29 | yes | +| `--theme-low-contrast-foreground-color` | 19 | 1 | yes | +| `--theme-top-foreground-color` | — | 34 | yes | +| `--theme-border-color` | — | 10 | yes | +| `--theme-foreground-accent-color` | — | 6 | yes | +| `--theme-top-low-contrast-foreground-color` | — | 5 | yes | +| `--theme-top-accent-color` | — | 2 | yes | +| `--theme-top-high-contrast-foreground-color` | — | 9 | yes | +| `--theme-high-contrast-foreground-color` | — | 3 | yes | +| `--theme-border-width` | — | 4 | yes | +| `--theme-pattern-image` | — | 0 | yes, but unused | +| `--theme-pattern-opacity` | — | 0 | yes, but unused | +| `--theme-font-color` | 1 | — | **no** — consumed upstream, never mapped here | + +--- + +## 5. Tokens + +`src/theme/_root.scss` — 256 lines. 116 custom-property definition sites across the +package. `[V 2026-09-02]` + +**Nothing in this package assigns the `--*-color-light` / `--*-color-dark` override +hooks.** They are populated at runtime from the backend Themes control panel +(`ISCVLTThemeDefinition`, see `src/types/theme.ts`). The `light-dark()` fallbacks are +therefore the shipped defaults, and every value below is exactly resolvable. + +### 5.1 Base palette + +| token | value | +|---|---| +| `--pure-white` | `#fff` | +| `--pure-black` | `#000` | +| `--gray-02` | `#f8f8f8` | +| `--gray-cool-05` | `#edeff0` | +| `--gray-10` | `#e6e6e6` | +| `--gray-50` | `#757575` | +| `--gray-80` | `#333` | +| `--brand-color` | `#f4822c` | +| `--brand-color-dark` | `#b55e1c` | +| `--brand-color-darker` | `#af5009` | + +### 5.2 Semantic palette + +Four families × four roles. Each is `light-dark(var(--x-light, FALLBACK), var(--x-dark, FALLBACK))`. + +| token | light | dark | +|---|---|---| +| `--primary-color` | `#ffffff` | `#000000` | +| `--primary-foreground-color` | `#000000` | `#ffffff` | +| `--primary-low-foreground-color` | `#333333` | `#f8f8f8` | +| `--primary-accent-color` | `#b55e1c` | `#f4822c` | +| `--secondary-color` | `#000000` | `#ffffff` | +| `--secondary-foreground-color` | `#ffffff` | `#000000` | +| `--secondary-low-foreground-color` | `#e6e6e6` | `#333333` | +| `--secondary-accent-color` | `#f4822c` | `#b55e1c` | +| `--accent-color` | `#f4822c` | `#b55e1c` | +| `--accent-foreground-color` | `#000000` | `#ffffff` | +| `--accent-low-foreground-color` | `#333333` | `#f8f8f8` | +| `--accent-accent-color` | `#000000` | `#ffffff` | +| `--neutral-color` | `#edeff0` | `#333333` | +| `--neutral-foreground-color` | `#000000` | `#ffffff` | +| `--neutral-low-foreground-color` | `#333333` | `#e6e6e6` | +| `--neutral-accent-color` | `#af5009` | `#f4822c` | + +### 5.3 Layout mappings + +| token | maps to | +|---|---| +| `--header-background` | `--primary-color` | +| `--header-foreground` | `--header-foreground-color`, falling back to `--primary-foreground-color` | +| `--footer-background` | `--secondary-color` | +| `--footer-foreground` | `--secondary-foreground-color` | +| `--fatmenu-background` | `--primary-color` | +| `--fatmenu-foreground` | `--primary-foreground-color` | +| `--breadcrumbs-background` | `--accent-color` | +| `--breadcrumbs-foreground` | `--accent-foreground-color` | +| `--search-background` | `--primary-color` | +| `--search-foreground` | `--primary-foreground-color` | + +`--header-foreground` keeps its name deliberately: upstream VLT's +`_bgcolor-blocks-layout.scss` reads that exact token. **Do not rename it.** + +### 5.4 Content-type colours + +| token | value | +|---|---| +| `--news-item-color` | `var(--pure-black)` | +| `--document-color` | `var(--accent-color)` | +| `--event-color` | `light-dark(#d32f2f, #d32f2f)` | +| `--file-color` | `light-dark(#1976d2, #1976d2)` | +| `--image-color` | `light-dark(#388e3c, #388e3c)` | + +> The last three are the only tokens whose default is a literal rather than a palette +> entry — they sit off the brand ramp. Each reads `--<type>-color-light` / +> `--<type>-color-dark` first, so a site theme can override them per colour mode; the +> shipped defaults are the same value in both. + +### 5.5 Links + +| token | value | +|---|---| +| `--link-foreground-color` | `var(--brand-color)` | +| `--link-color` | `var(--brand-color)` | +| `--link-color-hover` | `var(--brand-color-dark)` | +| `--color-link` | `var(--brand-color)` | +| `--color-link-hover` | `var(--brand-color-dark)` | + +Four tokens for two values — `--link-color`/`--color-link` and their `-hover` pairs are +duplicates carried for compatibility with two naming conventions. + +### 5.6 Container widths + +| token | value | +|---|---| +| `--narrow-container-width` | `940px !important` | +| `--default-container-width` | `940px !important` | +| `--layout-container-width` | `1440px !important` | + +`narrow` and `default` are the same value — see GAPS §3.1. + +### 5.7 Typography tokens + +| token | value | +|---|---| +| `--font-sans` | `system-ui, sans-serif` | +| `--custom-font` | `system-ui, sans-serif` | +| `--custom-main-font` | `system-ui, sans-serif` | +| `--font-body` | `system-ui, sans-serif` | + +All four resolve to the same stack; the theme ships no brand typeface. + +### 5.8 Colour modes + +`_root.scss` defines three `data-theme` states: + +| selector | `color-scheme` | notes | +|---|---|---| +| `[data-theme='light']` | `light` | | +| `[data-theme='dark']` | `dark` | | +| `[data-theme='high-contrast']` | `dark` | overrides all 16 semantic tokens plus links, content-type colours and both themes' borders | + +High-contrast palette: `--text-high-contrast: #fff`, `--bg-high-contrast: #000`, +`--border-high-contrast: #fff`, `--link-high-contrast: #fff333`, +`--accent-high-contrast: #fff`. It collapses every family onto black/white and forces +underlines on links and `.card-summary .title`. + +--- + +## 6. Type scale + +`src/theme/_variables.scss` — rem-based overrides of upstream VLT's typography tokens, +so the global `--font-scale` on `html` resizes theme-mixin text. `[V 2026-09-02]` + +| step | font-size | line-height | +|---|---|---| +| `2xs` | `0.75rem` | `0.875rem` | +| `xs` | `0.875rem` | `1rem` | +| `s` | `1.125rem` | `1.125rem` | +| `m` | `1.3125rem` | `1.5rem` | +| `l` | `1.5rem` | `1.875rem` | +| `xl` | `1.875rem` | `2.0625rem` | +| `2xl` | `2.0625rem` | `2.25rem` | +| `3xl` | `2.25rem` | `2.625rem` | +| `4xl` | `2.625rem` | `3rem` | +| `5xl` | `3rem` | `3.5rem` | +| `6xl` | `5rem` | `5.5rem` | + +`$font-sizes` and `$line-heights` both have 11 steps; every size has a line-height. + +Scalar overrides: `$font-size: 1.125rem`, `$line-height: 1.5rem`, `$heading1: 3.75rem`, +`$heading2: 1.875rem`, `$heading3: 1.5rem`, `$block-title-h2: 2.625rem`. + +> `$heading1` (`3.75rem`) does not correspond to any `$font-sizes` step, and the actual +> `h1.documentFirstHeading` rule uses `5xl` (`3rem`) instead. + +### 6.1 Heading rules + +`src/theme/_typography.scss`. `[V 2026-09-01]` + +| rule | size | weight | line-height | mobile override | +|---|---|---|---|---| +| `h1.documentFirstHeading` | `5xl` | 400 | `5xl` | `3xl` / `3xl`, margin-bottom `1rem` | +| `@mixin h2_base` → `h2` | `l` | 700 | `l` | centred, 2rem side margins | +| `@mixin h2_headline` → `h2.headline` | `xl` | 400 | `2xl` | centred, 2rem side margins | + +`h1`–`h6` are reset to `font-family: inherit`. Mobile breakpoint is +`$largest-mobile-screen` (from upstream VLT). + +--- + +## 7. Stories + +**103 stories.** The count and the classification below are current as of 2026-09-02; +`Public/Blocks/Inventory` is the authoritative live list of blocks, since it reads the +registry at runtime. + +| group | stories | covers | +|---|---|---| +| `Public/Blocks` | 56 | the four own blocks, the four listing variations, grid at 1–4 columns, separator, plus the inventory and gallery | +| `Public/Navigation` | 31 | the navigation bar, menu items, the fat menu and its parts | +| `Controlpanels/Themes` | 17 | admin UI — **excluded from the design-system export** | +| `Public/Views` | 4 | Document, News Item and Event composed through `DefaultView` | +| `Public/DocumentByLine` | 4 | the byline in isolation | +| `Public/Header` | 4 | accessibility controls | +| `Public/ThemeToggle` | 4 | the three colour modes | +| `Components/DropdownMenu` | 3 | | +| `Components/HeaderBarActions` | 1 | | + +Builder is `@storybook/react-webpack5` + Razzle (not Vite), configured in +`frontend/.storybook/`, run through `pnpm --filter @plone/volto storybook` — it borrows +Volto core's install from the `frontend/core` mrs-developer checkout. + +### 7.1 Where a story lives + +| the story covers | it lives | +|---|---| +| a component this package ships | beside the component | +| a component from a dependency, or nothing in particular | `src/storybook/` | + +So `Blocks/HeroBlock/HeroBlock.stories.tsx` sits with its block, while `gridBlock` +(Volto core) and `separator` (`@kitconcept/volto-separator-block`) have their stories in +`src/storybook/` — putting them under `components/Blocks/` would imply this package owns +them. The same split applies to fixtures: `Listing/fixtures.ts` stays with the listing +templates, `storybook/galleryFixtures.ts` and `storybook/viewFixtures.ts` do not. + +`src/storybook/README.md` states the rule alongside the code. + +### 7.2 Conventions + +- Stories render through the `withTheme` decorator (`src/storybook/withTheme.tsx`), which + supplies the colour mode, the block theme and optionally a site theme, and constrains + the story to `--default-container-width`, centred. Full-bleed stories opt out with + `parameters: { fullBleed: true }`. +- A component whose stylesheet is scoped to an ancestor must be rendered inside that + ancestor — see DESIGN_SYSTEM §7 for the cases where this bit. +- `Public/Blocks/Gallery` uses `RealStoreWrapper`, not `Wrapper`: `search` and + `eventCalendar` dispatch thunks on mount, which a plain mock store rejects. + +### 7.3 Static assets + +`frontend/.storybook/main.js` serves `packages/volto-light-theme/public` at the Storybook +root, so `public/images/example.jpg` is reachable as `/images/example.jpg`. Fixtures +currently point at `picsum.photos`; `public/images/README.md` lists the four places to +change to move them onto bundled assets. diff --git a/docs/design-system/previews/block-byline.html b/docs/design-system/previews/block-byline.html new file mode 100644 index 0000000..d258265 --- /dev/null +++ b/docs/design-system/previews/block-byline.html @@ -0,0 +1,105 @@ +<!-- @dsCard group="Components" --> +<!-- + Byline block — @simplesconsultoria/volto-light-theme + Real rendered markup captured from Storybook with computed styles inlined. + Remote images are replaced by a neutral placeholder. +--> +<meta charset="utf-8" /> +<title>Byline block + +
+

Byline block

+

Publication date, last-modified date and authors, each independently toggleable. It ships no stylesheet of its own, so it inherits page typography and colour entirely.

+

Variations

+
+
All fieldsall three toggles on
+
+
+
Light
+
+
Published:
Last modified:
By: Matheus NunesEricof
+
+
+
+
Dark
+
+
Published:
Last modified:
By: Matheus NunesEricof
+
+
+
+
+
+
Dates onlyshowAuthor: false
+
+
+
Light
+
+
Published:
Last modified:
+
+
+
+
Dark
+
+
Published:
Last modified:
+
+
+
+
+
+
Authors onlyboth dates off
+
+
+
Light
+
+
By: Matheus NunesEricof
+
+
+
+
Dark
+
+
By: Matheus NunesEricof
+
+
+
+
+
Captured from the built Storybook, not hand-written: each block is the real component output with its computed styles inlined, rendered through the withTheme decorator so the colour mode and the --theme-* mapping resolve exactly as they do on a page. Remote photography is replaced by a neutral placeholder. See DESIGN_SYSTEM.md §4.3 for why the decorator has to set the colour scheme in two places.
+
\ No newline at end of file diff --git a/docs/design-system/previews/block-hero.html b/docs/design-system/previews/block-hero.html new file mode 100644 index 0000000..425e377 --- /dev/null +++ b/docs/design-system/previews/block-hero.html @@ -0,0 +1,191 @@ + + + +Hero block + +
+

Hero block

+

The add-on's most configurable block: two variations, a dynamic schema whose fieldsets change with overwrite and variation, and 30 references to --theme-* — more than any other block, so it is the one most affected by the selected block theme.

+

Block theme “Primary” (default)

+
+
Flex, text leftvariation flex, imageSize: 50%
+
+
+
Light
+
+

News

Article

This is a Highlighted Title

A short description of the main topic, written to engage the reader.

Read more

Official source

  • Policy
  • Community
+
+
+
+
Dark
+
+

News

Article

This is a Highlighted Title

A short description of the main topic, written to engage the reader.

Read more

Official source

  • Policy
  • Community
+
+
+
+
+
+
Flex, text right, full widthfullWidth with no image
+
+
+
Light
+
+

News

Article

This is a Highlighted Title

A short description of the main topic, written to engage the reader.

Read more

Official source

  • Policy
  • Community
+
+
+
+
Dark
+
+

News

Article

This is a Highlighted Title

A short description of the main topic, written to engage the reader.

Read more

Official source

  • Policy
  • Community
+
+
+
+
+
+
Card, text leftvariation card, imageFit: contain
+
+
+
Light
+
+

News

Article

This is a Highlighted Title

A short description of the main topic, written to engage the reader.

Read more

Official source

  • Policy
  • Community
+
+
+
+
Dark
+
+

News

Article

This is a Highlighted Title

A short description of the main topic, written to engage the reader.

Read more

Official source

  • Policy
  • Community
+
+
+
+
+
+
Card, text rightvariation card, imageFit: cover
+
+
+
Light
+
+

News

Article

This is a Highlighted Title

A short description of the main topic, written to engage the reader.

Read more

Official source

  • Policy
  • Community
+
+
+
+
Dark
+
+

News

Article

This is a Highlighted Title

A short description of the main topic, written to engage the reader.

Read more

Official source

  • Policy
  • Community
+
+
+
+
+

Block theme “Brand” (brand)

+
+
Flex, text leftvariation flex, imageSize: 50%
+
+
+
Light
+
+

News

Article

This is a Highlighted Title

A short description of the main topic, written to engage the reader.

Read more

Official source

  • Policy
  • Community
+
+
+
+
Dark
+
+

News

Article

This is a Highlighted Title

A short description of the main topic, written to engage the reader.

Read more

Official source

  • Policy
  • Community
+
+
+
+
+
+
Flex, text right, full widthfullWidth with no image
+
+
+
Light
+
+

News

Article

This is a Highlighted Title

A short description of the main topic, written to engage the reader.

Read more

Official source

  • Policy
  • Community
+
+
+
+
Dark
+
+

News

Article

This is a Highlighted Title

A short description of the main topic, written to engage the reader.

Read more

Official source

  • Policy
  • Community
+
+
+
+
+
+
Card, text leftvariation card, imageFit: contain
+
+
+
Light
+
+

News

Article

This is a Highlighted Title

A short description of the main topic, written to engage the reader.

Read more

Official source

  • Policy
  • Community
+
+
+
+
Dark
+
+

News

Article

This is a Highlighted Title

A short description of the main topic, written to engage the reader.

Read more

Official source

  • Policy
  • Community
+
+
+
+
+
+
Card, text rightvariation card, imageFit: cover
+
+
+
Light
+
+

News

Article

This is a Highlighted Title

A short description of the main topic, written to engage the reader.

Read more

Official source

  • Policy
  • Community
+
+
+
+
Dark
+
+

News

Article

This is a Highlighted Title

A short description of the main topic, written to engage the reader.

Read more

Official source

  • Policy
  • Community
+
+
+
+
+
Captured from the built Storybook, not hand-written: each block is the real component output with its computed styles inlined, rendered through the withTheme decorator so the colour mode and the --theme-* mapping resolve exactly as they do on a page. Remote photography is replaced by a neutral placeholder. See DESIGN_SYSTEM.md §4.3 for why the decorator has to set the colour scheme in two places.
+
\ No newline at end of file diff --git a/docs/design-system/previews/block-main-image.html b/docs/design-system/previews/block-main-image.html new file mode 100644 index 0000000..77093a2 --- /dev/null +++ b/docs/design-system/previews/block-main-image.html @@ -0,0 +1,139 @@ + + + +Main Image block + +
+

Main Image block

+

Renders the page's preview_image_link at one of four alignments and three sizes. Its stylesheet references no colour token at all — only the three container widths — so it is unaffected by the block theme.

+

Variations

+
+
Defaultalign: center
+
+
+
Light
+
+
Council chamber during a public session
+
+
+
+
Dark
+
+
Council chamber during a public session
+
+
+
+
+
+
Full, with captionalign: full, title and description
+
+
+
Light
+
+
Council chamber during a public session
Public session attended by dozens of citizens

The council welcomed residents for the debate on the 2026 budget.

+
+
+
+
Dark
+
+
Council chamber during a public session
Public session attended by dozens of citizens

The council welcomed residents for the debate on the 2026 budget.

+
+
+
+
+
+
Left, smallalign: left, size: s
+
+
+
Light
+
+
Council chamber during a public session
Featured story

A short summary shown beside the image.

+
+
+
+
Dark
+
+
Council chamber during a public session
Featured story

A short summary shown beside the image.

+
+
+
+
+
+
Right, mediumalign: right, size: m
+
+
+
Light
+
+
Council chamber during a public session
+
+
+
+
Dark
+
+
Council chamber during a public session
+
+
+
+
+
+
Centre, overlaid captionalign: center, size: l
+
+
+
Light
+
+
Council chamber during a public session
Centred image with an overlaid caption

The description sits over the lower edge of the image.

+
+
+
+
Dark
+
+
Council chamber during a public session
Centred image with an overlaid caption

The description sits over the lower edge of the image.

+
+
+
+
+
Captured from the built Storybook, not hand-written: each block is the real component output with its computed styles inlined, rendered through the withTheme decorator so the colour mode and the --theme-* mapping resolve exactly as they do on a page. Remote photography is replaced by a neutral placeholder. See DESIGN_SYSTEM.md §4.3 for why the decorator has to set the colour scheme in two places.
+
\ No newline at end of file diff --git a/docs/design-system/previews/block-quote.html b/docs/design-system/previews/block-quote.html new file mode 100644 index 0000000..24028ed --- /dev/null +++ b/docs/design-system/previews/block-quote.html @@ -0,0 +1,157 @@ + + + +Quote block + +
+

Quote block

+

A Slate-bodied block with an optional attribution and two background styles. It is the only own block that reads semantic tokens (--accent-color) directly as well as --theme-*.

+

Block theme “Primary” (default)

+
+
TransparentbackgroundStyle: transparent
+
+
+
Light
+
+

Access to information is a fundamental right. Without it there is no full democracy and no real exercise of citizenship. This quotation shows how text behaves inside the block, with the theme typography applied.

Communications Specialist
+
+
+
+
Dark
+
+

Access to information is a fundamental right. Without it there is no full democracy and no real exercise of citizenship. This quotation shows how text behaves inside the block, with the theme typography applied.

Communications Specialist
+
+
+
+
+
+
FilledbackgroundStyle: filled
+
+
+
Light
+
+

Access to information is a fundamental right. Without it there is no full democracy and no real exercise of citizenship. This quotation shows how text behaves inside the block, with the theme typography applied.

Collective
+
+
+
+
Dark
+
+

Access to information is a fundamental right. Without it there is no full democracy and no real exercise of citizenship. This quotation shows how text behaves inside the block, with the theme typography applied.

Collective
+
+
+
+
+
+
No attributionauthor unset
+
+
+
Light
+
+

Access to information is a fundamental right. Without it there is no full democracy and no real exercise of citizenship. This quotation shows how text behaves inside the block, with the theme typography applied.

+
+
+
+
Dark
+
+

Access to information is a fundamental right. Without it there is no full democracy and no real exercise of citizenship. This quotation shows how text behaves inside the block, with the theme typography applied.

+
+
+
+
+

Block theme “Brand” (brand)

+
+
TransparentbackgroundStyle: transparent
+
+
+
Light
+
+

Access to information is a fundamental right. Without it there is no full democracy and no real exercise of citizenship. This quotation shows how text behaves inside the block, with the theme typography applied.

Communications Specialist
+
+
+
+
Dark
+
+

Access to information is a fundamental right. Without it there is no full democracy and no real exercise of citizenship. This quotation shows how text behaves inside the block, with the theme typography applied.

Communications Specialist
+
+
+
+
+
+
FilledbackgroundStyle: filled
+
+
+
Light
+
+

Access to information is a fundamental right. Without it there is no full democracy and no real exercise of citizenship. This quotation shows how text behaves inside the block, with the theme typography applied.

Collective
+
+
+
+
Dark
+
+

Access to information is a fundamental right. Without it there is no full democracy and no real exercise of citizenship. This quotation shows how text behaves inside the block, with the theme typography applied.

Collective
+
+
+
+
+
+
No attributionauthor unset
+
+
+
Light
+
+

Access to information is a fundamental right. Without it there is no full democracy and no real exercise of citizenship. This quotation shows how text behaves inside the block, with the theme typography applied.

+
+
+
+
Dark
+
+

Access to information is a fundamental right. Without it there is no full democracy and no real exercise of citizenship. This quotation shows how text behaves inside the block, with the theme typography applied.

+
+
+
+
+
Captured from the built Storybook, not hand-written: each block is the real component output with its computed styles inlined, rendered through the withTheme decorator so the colour mode and the --theme-* mapping resolve exactly as they do on a page. Remote photography is replaced by a neutral placeholder. See DESIGN_SYSTEM.md §4.3 for why the decorator has to set the colour scheme in two places.
+
\ No newline at end of file diff --git a/docs/design-system/previews/block-themes.html b/docs/design-system/previews/block-themes.html new file mode 100644 index 0000000..71f502f --- /dev/null +++ b/docs/design-system/previews/block-themes.html @@ -0,0 +1,246 @@ + + + +Block themes + + +
+

Block themes

+

+ Any block that calls defaultStylingSchema gets a + theme control. A theme is produced by + createThemeDefinition(name, label), which maps 14 semantic + --theme-* properties onto --block-theme-{name}-* tokens. + Adding a third theme downstream is one line plus a matching token block. +

+ +

The four layers

+
+
GroundThe block's own background and text — bg, text, low-contrast, accent-color.
+
High GroundElevated elements inside the block: cards, chips, teasers — the high-bg and top-* tokens.
+
BorderOptional edge — border and border-width.
+
PatternDecorative overlay — pattern-image, pattern-opacity. Defined as none/0; not yet consumed.
+
+ +

default — label “Primary”

+
+
+
defaultGround = primaryLight
+
+
Eyebrow · accent
+

Ground carries the block

+

Low-contrast foreground handles secondary copy, deck text and metadata across the block body.

+
+
High ground card
+
Elevated surfaces switch to the neutral family.
+
Top accent · 4.58
+
+
+
+
+
defaultGround = primaryDark
+
+
Eyebrow · accent
+

Ground carries the block

+

Low-contrast foreground handles secondary copy, deck text and metadata across the block body.

+
+
High ground card
+
Elevated surfaces switch to the neutral family.
+
Top accent · 4.86
+
+
+
+
+ +

brand — label “Brand”

+
+
+
brandGround = accentLight
+
+
Eyebrow · accent
+

Ground carries the block

+

Low-contrast foreground handles secondary copy, deck text and metadata across the block body.

+
+
High ground card
+
Elevated surfaces invert back to the primary family.
+
Top accent · 4.58
+
+
+
+
+
brandGround = accentDark
+
+
Eyebrow · accent
+

Ground carries the block

+

Low-contrast foreground handles secondary copy, deck text and metadata across the block body.

+
+
High ground card
+
Elevated surfaces invert back to the primary family.
+
Top accent · 8.08
+
+
+
+
+

+ brand sets border: unset with border-width: 0, + so it renders edge-to-edge — + hence no top rule above, unlike default’s 4px 0 0. +

+ +

Property mapping

+ + + + + + + + + + + + + + + + + + + + +
LayerSemantic propertyToken suffixdefaultbrandStatus
Ground--theme-colorbgprimaryaccentdefined
--theme-foreground-colortextprimary fgaccent fgdefined
--theme-high-contrast-foreground-colorhigh-contrastprimary fgaccent fgdefined
--theme-low-contrast-foreground-colorlow-contrastprimary lowaccent lowdefined
--theme-foreground-accent-coloraccent-colorprimary accaccent accdefined
High Ground--theme-high-contrast-colorhigh-bgneutralprimarydefined
--theme-top-foreground-colortop-textneutral fgprimary fgdefined
--theme-top-high-contrast-foreground-colortop-high-contrastneutral fgprimary fgdefined
--theme-top-low-contrast-foreground-colortop-low-contrastneutral lowprimary lowdefined
--theme-top-accent-colortop-accent-colorneutral accprimary accdefined
Border--theme-border-colorborderaccentunsetdefined
--theme-border-widthborder-width4px 0 00defined
Pattern--theme-pattern-imagepattern-imagenonenoneunused
--theme-pattern-opacitypattern-opacity00unused
+ +
+ All 14 mapped properties resolve in both themes. Four of them did not, and the + failure was silent: a var() with no definition and no fallback is invalid + at computed-value time, so the whole declaration is dropped rather than degraded. That + took out six focus outlines in _listing.scss reading + outline: 2px solid var(--theme-top-high-contrast-foreground-color), + leaving keyboard focus invisible on listing cards. + src/theme/blockThemes.test.ts now guards the contract. +
+
diff --git a/docs/design-system/previews/colors.html b/docs/design-system/previews/colors.html new file mode 100644 index 0000000..d2689a7 --- /dev/null +++ b/docs/design-system/previews/colors.html @@ -0,0 +1,338 @@ + + + +Colour palette + + +
+
+

Colour palette

+

+ Four semantic families × four roles, each defined as a + light-dark() pair. Nothing in the package assigns the + --*-light / --*-dark override hooks, so the values below + are what ships; a site configures its own through the Themes control panel. +

+
+ +

Base ramp

+

Raw values. Never referenced directly by components — always through a semantic token.

+
+
--pure-white#ffffff
+
--gray-02#f8f8f8
+
--gray-cool-05#edeff0
+
--gray-10#e6e6e6
+
--gray-50#757575
+
--gray-80#333333
+
--pure-black#000000
+
--brand-color#f4822c
+
--brand-color-dark#b55e1c
+
--brand-color-darker#af5009
+
+ +

Semantic families

+

+ Each family pairs a ground with a foreground, a low-contrast foreground for secondary + text, and an accent. Ratios are WCAG 2.1 against the family ground. +

+ +
+ +
+
primary — page surface
+
+
+
Light
+
+
Foreground · 21.0
+
Low contrast · 12.6
+
Accent · 4.58
+
+
+
--primary-color#ffffff
+
--primary-foreground-color#000000
+
--primary-low-foreground-color#333333
+
--primary-accent-color#b55e1c
+
+
+
+
Dark
+
+
Foreground · 21.0
+
Low contrast · 19.8
+
Accent · 8.08
+
+
+
--primary-color#000000
+
--primary-foreground-color#ffffff
+
--primary-low-foreground-color#f8f8f8
+
--primary-accent-color#f4822c
+
+
+
+
+ +
+
secondary — footer surface
+
+
+
Light
+
+
Foreground · 21.0
+
Low contrast · 16.8
+
Accent · 8.08
+
+
+
--secondary-color#000000
+
--secondary-foreground-color#ffffff
+
--secondary-low-foreground-color#e6e6e6
+
--secondary-accent-color#f4822c
+
+
+
+
Dark
+
+
Foreground · 21.0
+
Low contrast · 12.6
+
Accent · 4.58
+
+
+
--secondary-color#ffffff
+
--secondary-foreground-color#000000
+
--secondary-low-foreground-color#333333
+
--secondary-accent-color#b55e1c
+
+
+
+
+ +
+
accent — breadcrumbs, brand theme ground
+
+
+
Light
+
+
Foreground · 8.08
+
Low contrast · 4.86
+
Accent · 8.08
+
+
+
--accent-color#f4822c
+
--accent-foreground-color#000000
+
--accent-low-foreground-color#333333
+
--accent-accent-color#000000
+
+
+
+
Dark
+
+
Foreground · 4.58
+
Low contrast · 4.31 LARGE ONLY
+
Accent · 4.58
+
+
+
--accent-color#b55e1c
+
--accent-foreground-color#ffffff
+
--accent-low-foreground-color#f8f8f8
+
--accent-accent-color#ffffff
+
+
+
+
+ +
+
neutral — elevated cards, default theme high ground
+
+
+
Light
+
+
Foreground · 18.2
+
Low contrast · 10.9
+
Accent · 4.58
+
+
+
--neutral-color#edeff0
+
--neutral-foreground-color#000000
+
--neutral-low-foreground-color#333333
+
--neutral-accent-color#af5009
+
+
+
+
Dark
+
+
Foreground · 12.6
+
Low contrast · 10.1
+
Accent · 4.86
+
+
+
--neutral-color#333333
+
--neutral-foreground-color#ffffff
+
--neutral-low-foreground-color#e6e6e6
+
--neutral-accent-color#f4822c
+
+
+
+
+ +
+ +

Links & content types

+
+
--link-color#f4822c
+
--link-color-hover#b55e1c
+
--news-item-color#000000
+
--document-coloraccent
+
--event-color#d32f2f
+
--file-color#1976d2
+
--image-color#388e3c
+
+ +
+Every accent pairing now clears WCAG AA. The two that did not — + --secondary-accent-color in dark (2.60) and + --neutral-accent-color in light (2.25) — were the only accent + tokens carrying #f4822c in both modes. The first now swaps to + --brand-color-dark; the second needed a third shade, + --brand-color-darker (#af5009), because + #b55e1c reaches only 3.97 against #edeff0. Every value here + is a default that a site theme replaces. +
+
diff --git a/docs/design-system/previews/typography.html b/docs/design-system/previews/typography.html new file mode 100644 index 0000000..135309b --- /dev/null +++ b/docs/design-system/previews/typography.html @@ -0,0 +1,230 @@ + + + +Type scale + + +
+

Type scale

+

+ Eleven steps, declared in rem so the global --font-scale applied to + html resizes theme-mixin text without per-component edits. Pixel values + below assume a 16px root. +

+ +

Scale

+

+ $font-sizes and $line-heights both have 11 steps — + every size has a line-height. +

+
+
+
2xs
+
0.75rem · 12px0.875rem · 14px
+
Grade labels and fine print
+
+
+
xs
+
0.875rem · 14px1rem · 16px
+
Captions, metadata, tags
+
+
+
s
+
1.125rem · 18px1.125rem · 18px
+
Body copy — the base size
+
+
+
m
+
1.3125rem · 21px1.5rem · 24px
+
Lead paragraphs and decks
+
+
+
l
+
1.5rem · 24px1.875rem · 30px
+
Section heading — h2 base
+
+
+
xl
+
1.875rem · 30px2.0625rem · 33px
+
Headline h2
+
+
+
2xl
+
2.0625rem · 33px2.25rem · 36px
+
Display small
+
+
+
3xl
+
2.25rem · 36px2.625rem · 42px
+
Mobile page title
+
+
+
4xl
+
2.625rem · 42px3rem · 48px
+
Block title
+
+
+
5xl
+
3rem · 48px3.5rem · 56px
+
Page title
+
+
+
6xl
+
5rem · 80px5.5rem · 88px
+
Display
+
+
+ +

Heading rules

+

+ From _typography.scss. h1h6 are reset to + font-family: inherit. Mobile overrides fire at + $largest-mobile-screen and centre the text with 2rem side margins. +

+
+
+
h1.documentFirstHeading · 5xl / 400 / 5xl · mobile 3xl
+
Page title sits at the top of the document
+
+
+
@mixin h2_base → h2 · l / 700 / l · padding-top 2rem
+
Section heading inside a block
+
+
+
@mixin h2_headline → h2.headline · xl / 400 / 2xl
+
Headline variant used above block content
+
+
+ +

Scalar overrides

+

+ Upstream VLT declares these !default so add-ons can replace them. Each + override is an exact px→rem conversion at a 16px root, which is what lets the + global --font-scale resize text emitted by upstream mixins. +

+ + + + + + + + + + +
VariableUpstream !defaultOverrideExact
$font-size18px1.125rem
$line-height24px1.5rem
$heading160px3.75rem
$heading230px1.875rem
$heading324px1.5rem
$block-title-h242px2.625rem
+ +

Font stacks

+

All four tokens resolve to the same stack — the theme ships no brand typeface.

+
+
--font-sanssystem-ui, sans-serif
+
--font-bodysystem-ui, sans-serif
+
--custom-fontsystem-ui, sans-serif
+
--custom-main-fontsystem-ui, sans-serif — drives upstream $page-font
+
+ +
+ 2xs was the one real gap — a font-size with no line-height, so + map-get($line-heights, 2xs) returned null. It now has one. + --custom-main-font has no consumer in this package but is read twice by + upstream VLT, where it drives $page-font. Scale and heading rules: + INVENTORY.md §6. +
+
diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index 570b1b6..6ff188c 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -17,6 +17,8 @@ const addonAliases = Object.keys(registry.packages).map((o) => [ ]); module.exports = { + // Build output — see .prettierignore. + ignorePatterns: ['packages/*/dist'], extends: `${coreLocation}/packages/volto/.eslintrc`, rules: { 'import/no-unresolved': 1, diff --git a/frontend/.prettierignore b/frontend/.prettierignore index 16f05c7..c8ad5b6 100644 --- a/frontend/.prettierignore +++ b/frontend/.prettierignore @@ -1,3 +1,7 @@ .storybook CHANGELOG.md README.md + +# Build output — the design-system export emits .d.ts under packages/*/dist/src/, +# which the lint globs would otherwise pick up. +packages/*/dist diff --git a/frontend/.storybook/main.js b/frontend/.storybook/main.js index a645419..ca9edfe 100644 --- a/frontend/.storybook/main.js +++ b/frontend/.storybook/main.js @@ -61,6 +61,11 @@ const defaultRazzleOptions = { }; module.exports = { + // Served at the Storybook root, so a story can reference `/images/foo.jpg`. + // `packages/volto-light-theme/public` is the add-on's own asset folder; it + // ships with the package, so fixtures pointing at it work for anyone building + // this Storybook rather than depending on a remote host. + staticDirs: ['../packages/volto-light-theme/public'], stories: [ '../packages/**/*.mdx', '../packages/**/*.stories.@(js|jsx|ts|tsx)', diff --git a/frontend/.storybook/preview.jsx b/frontend/.storybook/preview.jsx index 1d2ac84..82cb06f 100644 --- a/frontend/.storybook/preview.jsx +++ b/frontend/.storybook/preview.jsx @@ -6,6 +6,11 @@ import enMessages from '@root/../locales/en.json'; import '@root/theme'; +import { + globalTypes as themeGlobalTypes, + withTheme, +} from '@simplesconsultoria/volto-light-theme/storybook/withTheme'; + export const parameters = { controls: { matchers: { @@ -15,7 +20,10 @@ export const parameters = { }, }; +export const globalTypes = themeGlobalTypes; + export const decorators = [ + withTheme, (Story) => ( diff --git a/frontend/packages/volto-light-theme/.gitignore b/frontend/packages/volto-light-theme/.gitignore index b462bc9..0f330c3 100644 --- a/frontend/packages/volto-light-theme/.gitignore +++ b/frontend/packages/volto-light-theme/.gitignore @@ -1,3 +1,5 @@ node_modules build -README.md +# Root README only — a bare rule here also swallowed src/storybook/README.md and +# public/images/README.md, both referenced by docs but never committed. +/README.md diff --git a/frontend/packages/volto-light-theme/.npmignore b/frontend/packages/volto-light-theme/.npmignore index 1758d8c..2730aae 100644 --- a/frontend/packages/volto-light-theme/.npmignore +++ b/frontend/packages/volto-light-theme/.npmignore @@ -1 +1,22 @@ tsconfig.dev.json + +# npm ignores .gitignore entirely once this file exists, so build products have +# to be named here too — the design-system export emits both (see +# .design-sync/NOTES.md) and neither belongs in the published package. +dist/ +index.d.ts +build/ + +# Stories, tests and their fixture assets. Consumed by Storybook and by the +# Claude Design export; a project installing this package renders none of them. +*.stories.tsx +*.test.* + +# Story-support modules. They import the bundled photographs, which are excluded +# above, so shipping them would publish broken imports — and no project renders +# them anyway. +src/storybook/ +**/fixtures.ts +**/mocks.ts +**/storyMocks.ts +**/storyDecorators.tsx diff --git a/frontend/packages/volto-light-theme/locales/de/LC_MESSAGES/volto.po b/frontend/packages/volto-light-theme/locales/de/LC_MESSAGES/volto.po index 796f3cf..dd06652 100644 --- a/frontend/packages/volto-light-theme/locales/de/LC_MESSAGES/volto.po +++ b/frontend/packages/volto-light-theme/locales/de/LC_MESSAGES/volto.po @@ -10,3 +10,749 @@ msgstr "" "Content-Type: \n" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" + +#. Default: "Accessibility options" +#: components/Header/MobileHeader/MobileTools +msgid "Accessibility options" +msgstr "" + +#. Default: "Actions" +#: components/Controlpanels/Themes/messages +msgid "Actions" +msgstr "" + +#. Default: "Add theme" +#: components/Controlpanels/Themes/messages +msgid "Add theme" +msgstr "" + +#. Default: "Alignment" +#: components/Blocks/Listing/schema +#: components/Blocks/MainImageBlock/schema +msgid "Alignment" +msgstr "" + +#. Default: "Alt Text" +#: components/Blocks/MainImageBlock/schema +msgid "Alt Text" +msgstr "" + +#. Default: "Leave empty if the image is purely decorative" +#: components/Blocks/MainImageBlock/schema +msgid "Alt text hint" +msgstr "" + +#. Default: "Describe the purpose of the image" +#: components/Blocks/MainImageBlock/schema +msgid "Alt text hint link text" +msgstr "" + +#. Default: "Attached Card" +#: index +msgid "Attached Card" +msgstr "" + +#. Default: "Author / Source" +#: components/Blocks/QuoteBlock/schema +msgid "Author / Source" +msgstr "" + +#. Default: "Auto-advance" +#: components/Blocks/Listing/schema +msgid "Auto-advance" +msgstr "" + +#. Default: "Auto-advance interval (ms)" +#: components/Blocks/Listing/schema +msgid "Auto-advance interval (ms)" +msgstr "" + +#. Default: "Back" +#: components/Controlpanels/Themes/messages +msgid "Back" +msgstr "" + +#. Default: "Background color" +#: components/Blocks/schema +msgid "Background color" +msgstr "" + +#. Default: "Background style" +#: components/Blocks/QuoteBlock/schema +msgid "Background style" +msgstr "" + +#. Default: "Block Width" +#: components/Blocks/HeroBlock/schema +#: components/Blocks/MainImageBlock/schema +msgid "Block Width" +msgstr "" + +#. Default: "Brand" +#: index +msgid "Brand" +msgstr "" + +#. Default: "Button link" +#: components/Blocks/HeroBlock/schema +msgid "Button link" +msgstr "" + +#. Default: "Button text" +#: components/Blocks/HeroBlock/schema +msgid "Button text" +msgstr "" + +#. Default: "The text shown inside the button (e.g. \"Read more\")." +#: components/Blocks/HeroBlock/schema +msgid "Button text description" +msgstr "" + +#. Default: "By" +#: components/DocumentByLine/DocumentByLine +msgid "By" +msgstr "" + +#. Default: "Byline" +#: components/Blocks/DocumentByLine/schema +#: index +msgid "Byline" +msgstr "" + +#. Default: "Call to Action" +#: components/Blocks/HeroBlock/schema +msgid "Call to Action" +msgstr "" + +#. Default: "Cancel" +#: components/Controlpanels/Themes/messages +msgid "Cancel" +msgstr "" + +#. Default: "Carousel" +#: components/Blocks/Listing/messages +#: components/Blocks/Listing/schema +#: index +msgid "Carousel" +msgstr "" + +#. Default: "Carousel controls" +#: components/Blocks/Listing/messages +msgid "Carousel controls" +msgstr "" + +#. Default: "Center" +#: components/Blocks/Listing/schema +msgid "Center" +msgstr "" + +#. Default: "Center bottom" +#: components/Blocks/Listing/schema +msgid "Center bottom" +msgstr "" + +#. Default: "Center top" +#: components/Blocks/Listing/schema +msgid "Center top" +msgstr "" + +#. Default: "Changes saved" +#: components/Controlpanels/Themes/messages +msgid "Changes saved" +msgstr "" + +#. Default: "Close menu" +#: components/Navigation/SCNavigation/SubMenu +msgid "Close menu" +msgstr "" + +#. Default: "Colours" +#: components/Controlpanels/Themes/messages +msgid "Colours" +msgstr "" + +#. Default: "Contain" +#: components/Blocks/HeroBlock/schema +msgid "Contain" +msgstr "" + +#. Default: "Content" +#: components/Blocks/HeroBlock/schema +msgid "Content" +msgstr "" + +#. Default: "Cover" +#: components/Blocks/HeroBlock/schema +msgid "Cover" +msgstr "" + +#. Default: "Criteria" +#: components/Widgets/QuerystringWidget +msgid "Criteria" +msgstr "" + +#. Default: "Delete" +#: components/Controlpanels/Themes/messages +msgid "Delete" +msgstr "" + +#. Default: "Delete this theme? Content still using it keeps the stored id but falls back to the default colours." +#: components/Controlpanels/Themes/messages +msgid "Delete this theme?" +msgstr "" + +#. Default: "Description" +#: components/Blocks/HeroBlock/schema +#: components/Blocks/MainImageBlock/schema +msgid "Description" +msgstr "" + +#. Default: "Duplicate" +#: components/Controlpanels/Themes/messages +msgid "Duplicate" +msgstr "" + +#. Default: "Duplicate of {name}" +#: components/Controlpanels/Themes/messages +msgid "Duplicate of {name}" +msgstr "" + +#. Default: "Edit" +#: components/Controlpanels/Themes/messages +msgid "Edit" +msgstr "" + +#. Default: "This is a Hero Block. Add an item in the sidebar to fill it automatically." +#: components/Blocks/HeroBlock/useHeroBlockContent +msgid "Empty hero block placeholder" +msgstr "" + +#. Default: "Error" +#: components/Controlpanels/Themes/messages +msgid "Error" +msgstr "" + +#. Default: "File type" +#: components/Blocks/HeroBlock/schema +msgid "File type" +msgstr "" + +#. Default: "E.g. PDF, Report, Article" +#: components/Blocks/HeroBlock/schema +msgid "File type description" +msgstr "" + +#. Default: "Filled" +#: components/Blocks/QuoteBlock/schema +msgid "Filled" +msgstr "" + +#. Default: "Flex / Decorative" +#: index +msgid "Flex / Decorative" +msgstr "" + +#. Default: "Footer text" +#: components/Blocks/HeroBlock/schema +msgid "Footer text" +msgstr "" + +#. Default: "Additional information displayed at the foot of the block." +#: components/Blocks/HeroBlock/schema +msgid "Footer text description" +msgstr "" + +#. Default: "Full width" +#: components/Blocks/HeroBlock/schema +msgid "Full width" +msgstr "" + +#. Default: "Let the text span the whole block when there is no image to show." +#: components/Blocks/HeroBlock/schema +msgid "Full width description" +msgstr "" + +#. Default: "Go to item {index}" +#: components/Blocks/Listing/messages +msgid "Go to item {index}" +msgstr "" + +#. Default: "Header text" +#: components/Blocks/HeroBlock/schema +msgid "Header text" +msgstr "" + +#. Default: "Small text displayed above the main title." +#: components/Blocks/HeroBlock/schema +msgid "Header text description" +msgstr "" + +#. Default: "Headline button link" +#: components/Blocks/Listing/schema +msgid "Headline button link" +msgstr "" + +#. Default: "Headline button text" +#: components/Blocks/Listing/schema +msgid "Headline button text" +msgstr "" + +#. Default: "Height (px)" +#: components/Blocks/Listing/schema +msgid "Height (px)" +msgstr "" + +#. Default: "Hero Block" +#: components/Blocks/HeroBlock/schema +#: index +msgid "Hero Block" +msgstr "" + +#. Default: "Explanatory text or summary displayed below the title." +#: components/Blocks/HeroBlock/schema +msgid "Hero description description" +msgstr "" + +#. Default: "Main highlighted title." +#: components/Blocks/HeroBlock/schema +msgid "Hero title description" +msgstr "" + +#. Default: "Hide image" +#: components/Blocks/HeroBlock/schema +msgid "Hide image" +msgstr "" + +#. Default: "Highlight" +#: index +msgid "Highlight" +msgstr "" + +#. Default: "Highlighted item" +#: components/Blocks/HeroBlock/schema +msgid "Highlighted item" +msgstr "" + +#. Default: "Select a content item to pull its data automatically (title, description, image)" +#: components/Blocks/HeroBlock/schema +msgid "Highlighted item description" +msgstr "" + +#. Default: "Id" +#: components/Controlpanels/Themes/messages +msgid "Id" +msgstr "" + +#. Default: "Image" +#: components/Blocks/HeroBlock/schema +msgid "Image" +msgstr "" + +#. Default: "Image fit" +#: components/Blocks/HeroBlock/schema +msgid "Image fit" +msgstr "" + +#. Default: "\"Cover\" fills the whole available space, cropping the edges of the image if needed. \"Contain\" fits the entire image inside the space, which may leave empty bands at the sides." +#: components/Blocks/HeroBlock/schema +msgid "Image fit description" +msgstr "" + +#. Default: "Image size" +#: components/Blocks/MainImageBlock/schema +msgid "Image size" +msgstr "" + +#. Default: "Image size / empty space" +#: components/Blocks/HeroBlock/schema +msgid "Image size / empty space" +msgstr "" + +#. Default: "Controls the width reserved for the image (even when there is none), which in turn sets the space for the text." +#: components/Blocks/HeroBlock/schema +msgid "Image size description" +msgstr "" + +#. Default: "50% (half)" +#: components/Blocks/HeroBlock/schema +msgid "Image size half" +msgstr "" + +#. Default: "0% (text full width)" +#: components/Blocks/HeroBlock/schema +msgid "Image size none" +msgstr "" + +#. Default: "Item batch size" +#: components/Widgets/QuerystringWidget +msgid "Item batch size" +msgstr "" + +#. Default: "Items" +#: components/Blocks/Listing/messages +msgid "Items" +msgstr "" + +#. Default: "Last modified" +#: components/DocumentByLine/DocumentByLine +msgid "Last modified" +msgstr "" + +#. Default: "Layout" +#: components/Blocks/HeroBlock/schema +msgid "Layout" +msgstr "" + +#. Default: "Left bottom" +#: components/Blocks/Listing/schema +msgid "Left bottom" +msgstr "" + +#. Default: "Left center" +#: components/Blocks/Listing/schema +msgid "Left center" +msgstr "" + +#. Default: "Left top" +#: components/Blocks/Listing/schema +msgid "Left top" +msgstr "" + +#. Default: "Link settings" +#: components/Blocks/MainImageBlock/schema +msgid "Link settings" +msgstr "" + +#. Default: "Link to" +#: components/Blocks/MainImageBlock/schema +msgid "Link to" +msgstr "" + +#. Default: "Main Image" +#: components/Blocks/MainImageBlock/schema +#: index +msgid "Main Image" +msgstr "" + +#. Default: "Media Carousel" +#: components/Blocks/Listing/messages +#: index +msgid "Media Carousel" +msgstr "" + +#. Default: "Media fit (object-fit)" +#: components/Blocks/Listing/schema +msgid "Media fit (object-fit)" +msgstr "" + +#. Default: "Name" +#: components/Controlpanels/Themes/messages +msgid "Name" +msgstr "" + +#. Default: "Next" +#: components/Blocks/Listing/messages +msgid "Next" +msgstr "" + +#. Default: "Next Page" +#: components/Blocks/Listing/ListingBody +msgid "Next Page" +msgstr "" + +#. Default: "No items found in this container." +#: components/Blocks/Listing/ListingBody +msgid "No items found in this container." +msgstr "" + +#. Default: "No preview image found on this page. Add a preview image to the page to display it in this block." +#: components/Blocks/MainImageBlock/View +msgid "No preview image found on this page." +msgstr "" + +#. Default: "No selection" +#: components/Widgets/QuerystringWidget +msgid "No selection" +msgstr "" + +#. Default: "Number of columns" +#: components/Blocks/Listing/schema +msgid "Number of columns" +msgstr "" + +#. Default: "Offset" +#: components/Widgets/QuerystringWidget +msgid "Offset" +msgstr "" + +#. Default: "Open in a new tab" +#: components/Blocks/MainImageBlock/schema +msgid "Open in a new tab" +msgstr "" + +#. Default: "Open menu" +#: components/Navigation/SCNavigation/MenuItem +msgid "Open menu" +msgstr "" + +#. Default: "Overwrite content" +#: components/Blocks/HeroBlock/schema +msgid "Overwrite content" +msgstr "" + +#. Default: "Check to customise the title, description and image instead of pulling them automatically from the highlighted item" +#: components/Blocks/HeroBlock/schema +msgid "Overwrite content description" +msgstr "" + +#. Default: "Pagination Navigation" +#: components/Blocks/Listing/ListingBody +msgid "Pagination Navigation" +msgstr "" + +#. Default: "Pagination Navigation for {headline}" +#: components/Blocks/Listing/ListingBody +msgid "Pagination Navigation for {headline}" +msgstr "" + +#. Default: "Paragraph (P)" +#: components/Blocks/HeroBlock/schema +msgid "Paragraph (P)" +msgstr "" + +#. Default: "Pause carousel" +#: components/Blocks/Listing/messages +msgid "Pause carousel" +msgstr "" + +#. Default: "Play carousel" +#: components/Blocks/Listing/messages +msgid "Play carousel" +msgstr "" + +#. Default: "Position (object-position)" +#: components/Blocks/Listing/schema +msgid "Position (object-position)" +msgstr "" + +#. Default: "Previous" +#: components/Blocks/Listing/messages +msgid "Previous" +msgstr "" + +#. Default: "Previous Page" +#: components/Blocks/Listing/ListingBody +msgid "Previous Page" +msgstr "" + +#. Default: "Primary" +#: index +msgid "Primary" +msgstr "" + +#. Default: "Published" +#: components/DocumentByLine/DocumentByLine +msgid "Published" +msgstr "" + +#. Default: "Quote" +#: components/Blocks/QuoteBlock/schema +#: index +msgid "Quote" +msgstr "" + +#. Default: "Result" +#: components/Blocks/Listing/ListingBody +msgid "Result" +msgstr "" + +#. Default: "Results limit" +#: components/Widgets/QuerystringWidget +msgid "Results limit" +msgstr "" + +#. Default: "Reversed order" +#: components/Widgets/QuerystringWidget +msgid "Reversed order" +msgstr "" + +#. Default: "Right bottom" +#: components/Blocks/Listing/schema +msgid "Right bottom" +msgstr "" + +#. Default: "Right center" +#: components/Blocks/Listing/schema +msgid "Right center" +msgstr "" + +#. Default: "Right top" +#: components/Blocks/Listing/schema +msgid "Right top" +msgstr "" + +#. Default: "Save" +#: components/Controlpanels/Themes/messages +msgid "Save" +msgstr "" + +#. Default: "Section" +#: components/Navigation/SCNavigation/SubMenuSection +msgid "Section" +msgstr "" + +#. Default: "Select a highlighted item" +#: components/Blocks/HeroBlock/useHeroBlockContent +msgid "Select a highlighted item" +msgstr "" + +#. Default: "Show author" +#: components/Blocks/DocumentByLine/schema +msgid "Show author" +msgstr "" + +#. Default: "Show button" +#: components/Blocks/HeroBlock/schema +msgid "Show button" +msgstr "" + +#. Default: "Show date" +#: components/Blocks/HeroBlock/schema +msgid "Show date" +msgstr "" + +#. Default: "Show last modification date" +#: components/Blocks/DocumentByLine/schema +msgid "Show last modification date" +msgstr "" + +#. Default: "Show publication date" +#: components/Blocks/DocumentByLine/schema +msgid "Show publication date" +msgstr "" + +#. Default: "Sort on" +#: components/Widgets/QuerystringWidget +msgid "Sort on" +msgstr "" + +#. Default: "Styling" +#: components/Blocks/MainImageBlock/schema +msgid "Styling" +msgstr "" + +#. Default: "Tags" +#: components/Blocks/HeroBlock/schema +msgid "Tags" +msgstr "" + +#. Default: "Text side" +#: components/Blocks/HeroBlock/schema +msgid "Text side" +msgstr "" + +#. Default: "Theme deleted" +#: components/Controlpanels/Themes/messages +msgid "Theme deleted" +msgstr "" + +#. Default: "Themes" +#: components/Controlpanels/Themes/messages +msgid "Themes" +msgstr "" + +#. Default: "Title" +#: components/Blocks/HeroBlock/schema +#: components/Blocks/MainImageBlock/schema +msgid "Title" +msgstr "" + +#. Default: "Title element" +#: components/Blocks/HeroBlock/schema +msgid "Title element" +msgstr "" + +#. Default: "H1 is the main page title, H2 the secondary title and H3 the tertiary one. P renders the title as a paragraph." +#: components/Blocks/HeroBlock/schema +msgid "Title element description" +msgstr "" + +#. Default: "Transparent" +#: components/Blocks/QuoteBlock/schema +msgid "Transparent" +msgstr "" + +#. Default: "Video" +#: components/Blocks/Listing/messages +msgid "Video" +msgstr "" + +#. Default: "Decrease font size" +#: components/AccessibilityControls/FontSizeControls +msgid "accessibilityDecreaseFontSize" +msgstr "" + +#. Default: "Read on hover" +#: components/AccessibilityControls/HoverReaderControls +msgid "accessibilityHoverReaderLabel" +msgstr "" + +#. Default: "Increase font size" +#: components/AccessibilityControls/FontSizeControls +msgid "accessibilityIncreaseFontSize" +msgstr "" + +#. Default: "Reader speed" +#: components/AccessibilityControls/HoverReaderControls +msgid "accessibilitySpeedLabel" +msgstr "" + +#. Default: "Dark" +#: components/ThemeToggle/ThemeToggle +msgid "darkTheme" +msgstr "" + +#. Default: "Toggle menu" +#: components/DropdownMenu/DropdownMenu +msgid "dropdownMenuToggle" +msgstr "" + +#. Default: "Accessibility menu" +#: components/HeaderBar/HeaderBar +msgid "headerBarAccessibilityMenu" +msgstr "" + +#. Default: "MENU" +#: components/HeaderBar/HeaderBar +msgid "headerBarMenuLabel" +msgstr "" + +#. Default: "High contrast" +#: components/ThemeToggle/ThemeToggle +msgid "highContrastTheme" +msgstr "" + +#. Default: "Light" +#: components/ThemeToggle/ThemeToggle +msgid "lightTheme" +msgstr "" + +#. Default: "Loading" +#: components/Blocks/Listing/ListingBody +msgid "loading" +msgstr "" + +#. Default: "of" +#: components/Blocks/Listing/ListingBody +msgid "of" +msgstr "" + +#. Default: "Toggle theme" +#: components/ThemeToggle/ThemeToggle +msgid "toggleTheme" +msgstr "" + +#. Default: "{count, plural, one {# column} other {# columns}}" +#: components/Blocks/Listing/schema +msgid "{count, plural, one {# column} other {# columns}}" +msgstr "" diff --git a/frontend/packages/volto-light-theme/locales/en/LC_MESSAGES/volto.po b/frontend/packages/volto-light-theme/locales/en/LC_MESSAGES/volto.po index 38c369e..2d1b687 100644 --- a/frontend/packages/volto-light-theme/locales/en/LC_MESSAGES/volto.po +++ b/frontend/packages/volto-light-theme/locales/en/LC_MESSAGES/volto.po @@ -10,3 +10,749 @@ msgstr "" "Content-Type: \n" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" + +#. Default: "Accessibility options" +#: components/Header/MobileHeader/MobileTools +msgid "Accessibility options" +msgstr "" + +#. Default: "Actions" +#: components/Controlpanels/Themes/messages +msgid "Actions" +msgstr "" + +#. Default: "Add theme" +#: components/Controlpanels/Themes/messages +msgid "Add theme" +msgstr "" + +#. Default: "Alignment" +#: components/Blocks/Listing/schema +#: components/Blocks/MainImageBlock/schema +msgid "Alignment" +msgstr "" + +#. Default: "Alt Text" +#: components/Blocks/MainImageBlock/schema +msgid "Alt Text" +msgstr "" + +#. Default: "Leave empty if the image is purely decorative" +#: components/Blocks/MainImageBlock/schema +msgid "Alt text hint" +msgstr "" + +#. Default: "Describe the purpose of the image" +#: components/Blocks/MainImageBlock/schema +msgid "Alt text hint link text" +msgstr "" + +#. Default: "Attached Card" +#: index +msgid "Attached Card" +msgstr "" + +#. Default: "Author / Source" +#: components/Blocks/QuoteBlock/schema +msgid "Author / Source" +msgstr "" + +#. Default: "Auto-advance" +#: components/Blocks/Listing/schema +msgid "Auto-advance" +msgstr "" + +#. Default: "Auto-advance interval (ms)" +#: components/Blocks/Listing/schema +msgid "Auto-advance interval (ms)" +msgstr "" + +#. Default: "Back" +#: components/Controlpanels/Themes/messages +msgid "Back" +msgstr "" + +#. Default: "Background color" +#: components/Blocks/schema +msgid "Background color" +msgstr "" + +#. Default: "Background style" +#: components/Blocks/QuoteBlock/schema +msgid "Background style" +msgstr "" + +#. Default: "Block Width" +#: components/Blocks/HeroBlock/schema +#: components/Blocks/MainImageBlock/schema +msgid "Block Width" +msgstr "" + +#. Default: "Brand" +#: index +msgid "Brand" +msgstr "" + +#. Default: "Button link" +#: components/Blocks/HeroBlock/schema +msgid "Button link" +msgstr "" + +#. Default: "Button text" +#: components/Blocks/HeroBlock/schema +msgid "Button text" +msgstr "" + +#. Default: "The text shown inside the button (e.g. \"Read more\")." +#: components/Blocks/HeroBlock/schema +msgid "Button text description" +msgstr "" + +#. Default: "By" +#: components/DocumentByLine/DocumentByLine +msgid "By" +msgstr "" + +#. Default: "Byline" +#: components/Blocks/DocumentByLine/schema +#: index +msgid "Byline" +msgstr "" + +#. Default: "Call to Action" +#: components/Blocks/HeroBlock/schema +msgid "Call to Action" +msgstr "" + +#. Default: "Cancel" +#: components/Controlpanels/Themes/messages +msgid "Cancel" +msgstr "" + +#. Default: "Carousel" +#: components/Blocks/Listing/messages +#: components/Blocks/Listing/schema +#: index +msgid "Carousel" +msgstr "" + +#. Default: "Carousel controls" +#: components/Blocks/Listing/messages +msgid "Carousel controls" +msgstr "" + +#. Default: "Center" +#: components/Blocks/Listing/schema +msgid "Center" +msgstr "" + +#. Default: "Center bottom" +#: components/Blocks/Listing/schema +msgid "Center bottom" +msgstr "" + +#. Default: "Center top" +#: components/Blocks/Listing/schema +msgid "Center top" +msgstr "" + +#. Default: "Changes saved" +#: components/Controlpanels/Themes/messages +msgid "Changes saved" +msgstr "" + +#. Default: "Close menu" +#: components/Navigation/SCNavigation/SubMenu +msgid "Close menu" +msgstr "" + +#. Default: "Colours" +#: components/Controlpanels/Themes/messages +msgid "Colours" +msgstr "" + +#. Default: "Contain" +#: components/Blocks/HeroBlock/schema +msgid "Contain" +msgstr "" + +#. Default: "Content" +#: components/Blocks/HeroBlock/schema +msgid "Content" +msgstr "" + +#. Default: "Cover" +#: components/Blocks/HeroBlock/schema +msgid "Cover" +msgstr "" + +#. Default: "Criteria" +#: components/Widgets/QuerystringWidget +msgid "Criteria" +msgstr "" + +#. Default: "Delete" +#: components/Controlpanels/Themes/messages +msgid "Delete" +msgstr "" + +#. Default: "Delete this theme? Content still using it keeps the stored id but falls back to the default colours." +#: components/Controlpanels/Themes/messages +msgid "Delete this theme?" +msgstr "" + +#. Default: "Description" +#: components/Blocks/HeroBlock/schema +#: components/Blocks/MainImageBlock/schema +msgid "Description" +msgstr "" + +#. Default: "Duplicate" +#: components/Controlpanels/Themes/messages +msgid "Duplicate" +msgstr "" + +#. Default: "Duplicate of {name}" +#: components/Controlpanels/Themes/messages +msgid "Duplicate of {name}" +msgstr "" + +#. Default: "Edit" +#: components/Controlpanels/Themes/messages +msgid "Edit" +msgstr "" + +#. Default: "This is a Hero Block. Add an item in the sidebar to fill it automatically." +#: components/Blocks/HeroBlock/useHeroBlockContent +msgid "Empty hero block placeholder" +msgstr "" + +#. Default: "Error" +#: components/Controlpanels/Themes/messages +msgid "Error" +msgstr "" + +#. Default: "File type" +#: components/Blocks/HeroBlock/schema +msgid "File type" +msgstr "" + +#. Default: "E.g. PDF, Report, Article" +#: components/Blocks/HeroBlock/schema +msgid "File type description" +msgstr "" + +#. Default: "Filled" +#: components/Blocks/QuoteBlock/schema +msgid "Filled" +msgstr "" + +#. Default: "Flex / Decorative" +#: index +msgid "Flex / Decorative" +msgstr "" + +#. Default: "Footer text" +#: components/Blocks/HeroBlock/schema +msgid "Footer text" +msgstr "" + +#. Default: "Additional information displayed at the foot of the block." +#: components/Blocks/HeroBlock/schema +msgid "Footer text description" +msgstr "" + +#. Default: "Full width" +#: components/Blocks/HeroBlock/schema +msgid "Full width" +msgstr "" + +#. Default: "Let the text span the whole block when there is no image to show." +#: components/Blocks/HeroBlock/schema +msgid "Full width description" +msgstr "" + +#. Default: "Go to item {index}" +#: components/Blocks/Listing/messages +msgid "Go to item {index}" +msgstr "" + +#. Default: "Header text" +#: components/Blocks/HeroBlock/schema +msgid "Header text" +msgstr "" + +#. Default: "Small text displayed above the main title." +#: components/Blocks/HeroBlock/schema +msgid "Header text description" +msgstr "" + +#. Default: "Headline button link" +#: components/Blocks/Listing/schema +msgid "Headline button link" +msgstr "" + +#. Default: "Headline button text" +#: components/Blocks/Listing/schema +msgid "Headline button text" +msgstr "" + +#. Default: "Height (px)" +#: components/Blocks/Listing/schema +msgid "Height (px)" +msgstr "" + +#. Default: "Hero Block" +#: components/Blocks/HeroBlock/schema +#: index +msgid "Hero Block" +msgstr "" + +#. Default: "Explanatory text or summary displayed below the title." +#: components/Blocks/HeroBlock/schema +msgid "Hero description description" +msgstr "" + +#. Default: "Main highlighted title." +#: components/Blocks/HeroBlock/schema +msgid "Hero title description" +msgstr "" + +#. Default: "Hide image" +#: components/Blocks/HeroBlock/schema +msgid "Hide image" +msgstr "" + +#. Default: "Highlight" +#: index +msgid "Highlight" +msgstr "" + +#. Default: "Highlighted item" +#: components/Blocks/HeroBlock/schema +msgid "Highlighted item" +msgstr "" + +#. Default: "Select a content item to pull its data automatically (title, description, image)" +#: components/Blocks/HeroBlock/schema +msgid "Highlighted item description" +msgstr "" + +#. Default: "Id" +#: components/Controlpanels/Themes/messages +msgid "Id" +msgstr "" + +#. Default: "Image" +#: components/Blocks/HeroBlock/schema +msgid "Image" +msgstr "" + +#. Default: "Image fit" +#: components/Blocks/HeroBlock/schema +msgid "Image fit" +msgstr "" + +#. Default: "\"Cover\" fills the whole available space, cropping the edges of the image if needed. \"Contain\" fits the entire image inside the space, which may leave empty bands at the sides." +#: components/Blocks/HeroBlock/schema +msgid "Image fit description" +msgstr "" + +#. Default: "Image size" +#: components/Blocks/MainImageBlock/schema +msgid "Image size" +msgstr "" + +#. Default: "Image size / empty space" +#: components/Blocks/HeroBlock/schema +msgid "Image size / empty space" +msgstr "" + +#. Default: "Controls the width reserved for the image (even when there is none), which in turn sets the space for the text." +#: components/Blocks/HeroBlock/schema +msgid "Image size description" +msgstr "" + +#. Default: "50% (half)" +#: components/Blocks/HeroBlock/schema +msgid "Image size half" +msgstr "" + +#. Default: "0% (text full width)" +#: components/Blocks/HeroBlock/schema +msgid "Image size none" +msgstr "" + +#. Default: "Item batch size" +#: components/Widgets/QuerystringWidget +msgid "Item batch size" +msgstr "" + +#. Default: "Items" +#: components/Blocks/Listing/messages +msgid "Items" +msgstr "" + +#. Default: "Last modified" +#: components/DocumentByLine/DocumentByLine +msgid "Last modified" +msgstr "" + +#. Default: "Layout" +#: components/Blocks/HeroBlock/schema +msgid "Layout" +msgstr "" + +#. Default: "Left bottom" +#: components/Blocks/Listing/schema +msgid "Left bottom" +msgstr "" + +#. Default: "Left center" +#: components/Blocks/Listing/schema +msgid "Left center" +msgstr "" + +#. Default: "Left top" +#: components/Blocks/Listing/schema +msgid "Left top" +msgstr "" + +#. Default: "Link settings" +#: components/Blocks/MainImageBlock/schema +msgid "Link settings" +msgstr "" + +#. Default: "Link to" +#: components/Blocks/MainImageBlock/schema +msgid "Link to" +msgstr "" + +#. Default: "Main Image" +#: components/Blocks/MainImageBlock/schema +#: index +msgid "Main Image" +msgstr "" + +#. Default: "Media Carousel" +#: components/Blocks/Listing/messages +#: index +msgid "Media Carousel" +msgstr "" + +#. Default: "Media fit (object-fit)" +#: components/Blocks/Listing/schema +msgid "Media fit (object-fit)" +msgstr "" + +#. Default: "Name" +#: components/Controlpanels/Themes/messages +msgid "Name" +msgstr "" + +#. Default: "Next" +#: components/Blocks/Listing/messages +msgid "Next" +msgstr "" + +#. Default: "Next Page" +#: components/Blocks/Listing/ListingBody +msgid "Next Page" +msgstr "" + +#. Default: "No items found in this container." +#: components/Blocks/Listing/ListingBody +msgid "No items found in this container." +msgstr "" + +#. Default: "No preview image found on this page. Add a preview image to the page to display it in this block." +#: components/Blocks/MainImageBlock/View +msgid "No preview image found on this page." +msgstr "" + +#. Default: "No selection" +#: components/Widgets/QuerystringWidget +msgid "No selection" +msgstr "" + +#. Default: "Number of columns" +#: components/Blocks/Listing/schema +msgid "Number of columns" +msgstr "" + +#. Default: "Offset" +#: components/Widgets/QuerystringWidget +msgid "Offset" +msgstr "" + +#. Default: "Open in a new tab" +#: components/Blocks/MainImageBlock/schema +msgid "Open in a new tab" +msgstr "" + +#. Default: "Open menu" +#: components/Navigation/SCNavigation/MenuItem +msgid "Open menu" +msgstr "" + +#. Default: "Overwrite content" +#: components/Blocks/HeroBlock/schema +msgid "Overwrite content" +msgstr "" + +#. Default: "Check to customise the title, description and image instead of pulling them automatically from the highlighted item" +#: components/Blocks/HeroBlock/schema +msgid "Overwrite content description" +msgstr "" + +#. Default: "Pagination Navigation" +#: components/Blocks/Listing/ListingBody +msgid "Pagination Navigation" +msgstr "" + +#. Default: "Pagination Navigation for {headline}" +#: components/Blocks/Listing/ListingBody +msgid "Pagination Navigation for {headline}" +msgstr "" + +#. Default: "Paragraph (P)" +#: components/Blocks/HeroBlock/schema +msgid "Paragraph (P)" +msgstr "" + +#. Default: "Pause carousel" +#: components/Blocks/Listing/messages +msgid "Pause carousel" +msgstr "" + +#. Default: "Play carousel" +#: components/Blocks/Listing/messages +msgid "Play carousel" +msgstr "" + +#. Default: "Position (object-position)" +#: components/Blocks/Listing/schema +msgid "Position (object-position)" +msgstr "" + +#. Default: "Previous" +#: components/Blocks/Listing/messages +msgid "Previous" +msgstr "" + +#. Default: "Previous Page" +#: components/Blocks/Listing/ListingBody +msgid "Previous Page" +msgstr "" + +#. Default: "Primary" +#: index +msgid "Primary" +msgstr "" + +#. Default: "Published" +#: components/DocumentByLine/DocumentByLine +msgid "Published" +msgstr "" + +#. Default: "Quote" +#: components/Blocks/QuoteBlock/schema +#: index +msgid "Quote" +msgstr "" + +#. Default: "Result" +#: components/Blocks/Listing/ListingBody +msgid "Result" +msgstr "" + +#. Default: "Results limit" +#: components/Widgets/QuerystringWidget +msgid "Results limit" +msgstr "" + +#. Default: "Reversed order" +#: components/Widgets/QuerystringWidget +msgid "Reversed order" +msgstr "" + +#. Default: "Right bottom" +#: components/Blocks/Listing/schema +msgid "Right bottom" +msgstr "" + +#. Default: "Right center" +#: components/Blocks/Listing/schema +msgid "Right center" +msgstr "" + +#. Default: "Right top" +#: components/Blocks/Listing/schema +msgid "Right top" +msgstr "" + +#. Default: "Save" +#: components/Controlpanels/Themes/messages +msgid "Save" +msgstr "" + +#. Default: "Section" +#: components/Navigation/SCNavigation/SubMenuSection +msgid "Section" +msgstr "" + +#. Default: "Select a highlighted item" +#: components/Blocks/HeroBlock/useHeroBlockContent +msgid "Select a highlighted item" +msgstr "" + +#. Default: "Show author" +#: components/Blocks/DocumentByLine/schema +msgid "Show author" +msgstr "" + +#. Default: "Show button" +#: components/Blocks/HeroBlock/schema +msgid "Show button" +msgstr "" + +#. Default: "Show date" +#: components/Blocks/HeroBlock/schema +msgid "Show date" +msgstr "" + +#. Default: "Show last modification date" +#: components/Blocks/DocumentByLine/schema +msgid "Show last modification date" +msgstr "" + +#. Default: "Show publication date" +#: components/Blocks/DocumentByLine/schema +msgid "Show publication date" +msgstr "" + +#. Default: "Sort on" +#: components/Widgets/QuerystringWidget +msgid "Sort on" +msgstr "" + +#. Default: "Styling" +#: components/Blocks/MainImageBlock/schema +msgid "Styling" +msgstr "" + +#. Default: "Tags" +#: components/Blocks/HeroBlock/schema +msgid "Tags" +msgstr "" + +#. Default: "Text side" +#: components/Blocks/HeroBlock/schema +msgid "Text side" +msgstr "" + +#. Default: "Theme deleted" +#: components/Controlpanels/Themes/messages +msgid "Theme deleted" +msgstr "" + +#. Default: "Themes" +#: components/Controlpanels/Themes/messages +msgid "Themes" +msgstr "" + +#. Default: "Title" +#: components/Blocks/HeroBlock/schema +#: components/Blocks/MainImageBlock/schema +msgid "Title" +msgstr "" + +#. Default: "Title element" +#: components/Blocks/HeroBlock/schema +msgid "Title element" +msgstr "" + +#. Default: "H1 is the main page title, H2 the secondary title and H3 the tertiary one. P renders the title as a paragraph." +#: components/Blocks/HeroBlock/schema +msgid "Title element description" +msgstr "" + +#. Default: "Transparent" +#: components/Blocks/QuoteBlock/schema +msgid "Transparent" +msgstr "" + +#. Default: "Video" +#: components/Blocks/Listing/messages +msgid "Video" +msgstr "" + +#. Default: "Decrease font size" +#: components/AccessibilityControls/FontSizeControls +msgid "accessibilityDecreaseFontSize" +msgstr "" + +#. Default: "Read on hover" +#: components/AccessibilityControls/HoverReaderControls +msgid "accessibilityHoverReaderLabel" +msgstr "" + +#. Default: "Increase font size" +#: components/AccessibilityControls/FontSizeControls +msgid "accessibilityIncreaseFontSize" +msgstr "" + +#. Default: "Reader speed" +#: components/AccessibilityControls/HoverReaderControls +msgid "accessibilitySpeedLabel" +msgstr "" + +#. Default: "Dark" +#: components/ThemeToggle/ThemeToggle +msgid "darkTheme" +msgstr "" + +#. Default: "Toggle menu" +#: components/DropdownMenu/DropdownMenu +msgid "dropdownMenuToggle" +msgstr "" + +#. Default: "Accessibility menu" +#: components/HeaderBar/HeaderBar +msgid "headerBarAccessibilityMenu" +msgstr "" + +#. Default: "MENU" +#: components/HeaderBar/HeaderBar +msgid "headerBarMenuLabel" +msgstr "" + +#. Default: "High contrast" +#: components/ThemeToggle/ThemeToggle +msgid "highContrastTheme" +msgstr "" + +#. Default: "Light" +#: components/ThemeToggle/ThemeToggle +msgid "lightTheme" +msgstr "" + +#. Default: "Loading" +#: components/Blocks/Listing/ListingBody +msgid "loading" +msgstr "" + +#. Default: "of" +#: components/Blocks/Listing/ListingBody +msgid "of" +msgstr "" + +#. Default: "Toggle theme" +#: components/ThemeToggle/ThemeToggle +msgid "toggleTheme" +msgstr "" + +#. Default: "{count, plural, one {# column} other {# columns}}" +#: components/Blocks/Listing/schema +msgid "{count, plural, one {# column} other {# columns}}" +msgstr "" diff --git a/frontend/packages/volto-light-theme/locales/es/LC_MESSAGES/volto.po b/frontend/packages/volto-light-theme/locales/es/LC_MESSAGES/volto.po index 7288b05..7149f13 100644 --- a/frontend/packages/volto-light-theme/locales/es/LC_MESSAGES/volto.po +++ b/frontend/packages/volto-light-theme/locales/es/LC_MESSAGES/volto.po @@ -17,3 +17,749 @@ msgstr "" "Domain: volto\n" "X-Is-Fallback-For: es-ar es-bo es-cl es-co es-cr es-do es-ec es-es es-sv es-gt es-hn es-mx es-ni es-pa es-py es-pe es-pr es-us es-uy es-ve\n" "X-Generator: Poedit 2.2.1\n" + +#. Default: "Accessibility options" +#: components/Header/MobileHeader/MobileTools +msgid "Accessibility options" +msgstr "" + +#. Default: "Actions" +#: components/Controlpanels/Themes/messages +msgid "Actions" +msgstr "" + +#. Default: "Add theme" +#: components/Controlpanels/Themes/messages +msgid "Add theme" +msgstr "" + +#. Default: "Alignment" +#: components/Blocks/Listing/schema +#: components/Blocks/MainImageBlock/schema +msgid "Alignment" +msgstr "" + +#. Default: "Alt Text" +#: components/Blocks/MainImageBlock/schema +msgid "Alt Text" +msgstr "" + +#. Default: "Leave empty if the image is purely decorative" +#: components/Blocks/MainImageBlock/schema +msgid "Alt text hint" +msgstr "" + +#. Default: "Describe the purpose of the image" +#: components/Blocks/MainImageBlock/schema +msgid "Alt text hint link text" +msgstr "" + +#. Default: "Attached Card" +#: index +msgid "Attached Card" +msgstr "" + +#. Default: "Author / Source" +#: components/Blocks/QuoteBlock/schema +msgid "Author / Source" +msgstr "" + +#. Default: "Auto-advance" +#: components/Blocks/Listing/schema +msgid "Auto-advance" +msgstr "" + +#. Default: "Auto-advance interval (ms)" +#: components/Blocks/Listing/schema +msgid "Auto-advance interval (ms)" +msgstr "" + +#. Default: "Back" +#: components/Controlpanels/Themes/messages +msgid "Back" +msgstr "" + +#. Default: "Background color" +#: components/Blocks/schema +msgid "Background color" +msgstr "" + +#. Default: "Background style" +#: components/Blocks/QuoteBlock/schema +msgid "Background style" +msgstr "" + +#. Default: "Block Width" +#: components/Blocks/HeroBlock/schema +#: components/Blocks/MainImageBlock/schema +msgid "Block Width" +msgstr "" + +#. Default: "Brand" +#: index +msgid "Brand" +msgstr "" + +#. Default: "Button link" +#: components/Blocks/HeroBlock/schema +msgid "Button link" +msgstr "" + +#. Default: "Button text" +#: components/Blocks/HeroBlock/schema +msgid "Button text" +msgstr "" + +#. Default: "The text shown inside the button (e.g. \"Read more\")." +#: components/Blocks/HeroBlock/schema +msgid "Button text description" +msgstr "" + +#. Default: "By" +#: components/DocumentByLine/DocumentByLine +msgid "By" +msgstr "" + +#. Default: "Byline" +#: components/Blocks/DocumentByLine/schema +#: index +msgid "Byline" +msgstr "" + +#. Default: "Call to Action" +#: components/Blocks/HeroBlock/schema +msgid "Call to Action" +msgstr "" + +#. Default: "Cancel" +#: components/Controlpanels/Themes/messages +msgid "Cancel" +msgstr "" + +#. Default: "Carousel" +#: components/Blocks/Listing/messages +#: components/Blocks/Listing/schema +#: index +msgid "Carousel" +msgstr "" + +#. Default: "Carousel controls" +#: components/Blocks/Listing/messages +msgid "Carousel controls" +msgstr "" + +#. Default: "Center" +#: components/Blocks/Listing/schema +msgid "Center" +msgstr "" + +#. Default: "Center bottom" +#: components/Blocks/Listing/schema +msgid "Center bottom" +msgstr "" + +#. Default: "Center top" +#: components/Blocks/Listing/schema +msgid "Center top" +msgstr "" + +#. Default: "Changes saved" +#: components/Controlpanels/Themes/messages +msgid "Changes saved" +msgstr "" + +#. Default: "Close menu" +#: components/Navigation/SCNavigation/SubMenu +msgid "Close menu" +msgstr "" + +#. Default: "Colours" +#: components/Controlpanels/Themes/messages +msgid "Colours" +msgstr "" + +#. Default: "Contain" +#: components/Blocks/HeroBlock/schema +msgid "Contain" +msgstr "" + +#. Default: "Content" +#: components/Blocks/HeroBlock/schema +msgid "Content" +msgstr "" + +#. Default: "Cover" +#: components/Blocks/HeroBlock/schema +msgid "Cover" +msgstr "" + +#. Default: "Criteria" +#: components/Widgets/QuerystringWidget +msgid "Criteria" +msgstr "" + +#. Default: "Delete" +#: components/Controlpanels/Themes/messages +msgid "Delete" +msgstr "" + +#. Default: "Delete this theme? Content still using it keeps the stored id but falls back to the default colours." +#: components/Controlpanels/Themes/messages +msgid "Delete this theme?" +msgstr "" + +#. Default: "Description" +#: components/Blocks/HeroBlock/schema +#: components/Blocks/MainImageBlock/schema +msgid "Description" +msgstr "" + +#. Default: "Duplicate" +#: components/Controlpanels/Themes/messages +msgid "Duplicate" +msgstr "" + +#. Default: "Duplicate of {name}" +#: components/Controlpanels/Themes/messages +msgid "Duplicate of {name}" +msgstr "" + +#. Default: "Edit" +#: components/Controlpanels/Themes/messages +msgid "Edit" +msgstr "" + +#. Default: "This is a Hero Block. Add an item in the sidebar to fill it automatically." +#: components/Blocks/HeroBlock/useHeroBlockContent +msgid "Empty hero block placeholder" +msgstr "" + +#. Default: "Error" +#: components/Controlpanels/Themes/messages +msgid "Error" +msgstr "" + +#. Default: "File type" +#: components/Blocks/HeroBlock/schema +msgid "File type" +msgstr "" + +#. Default: "E.g. PDF, Report, Article" +#: components/Blocks/HeroBlock/schema +msgid "File type description" +msgstr "" + +#. Default: "Filled" +#: components/Blocks/QuoteBlock/schema +msgid "Filled" +msgstr "" + +#. Default: "Flex / Decorative" +#: index +msgid "Flex / Decorative" +msgstr "" + +#. Default: "Footer text" +#: components/Blocks/HeroBlock/schema +msgid "Footer text" +msgstr "" + +#. Default: "Additional information displayed at the foot of the block." +#: components/Blocks/HeroBlock/schema +msgid "Footer text description" +msgstr "" + +#. Default: "Full width" +#: components/Blocks/HeroBlock/schema +msgid "Full width" +msgstr "" + +#. Default: "Let the text span the whole block when there is no image to show." +#: components/Blocks/HeroBlock/schema +msgid "Full width description" +msgstr "" + +#. Default: "Go to item {index}" +#: components/Blocks/Listing/messages +msgid "Go to item {index}" +msgstr "" + +#. Default: "Header text" +#: components/Blocks/HeroBlock/schema +msgid "Header text" +msgstr "" + +#. Default: "Small text displayed above the main title." +#: components/Blocks/HeroBlock/schema +msgid "Header text description" +msgstr "" + +#. Default: "Headline button link" +#: components/Blocks/Listing/schema +msgid "Headline button link" +msgstr "" + +#. Default: "Headline button text" +#: components/Blocks/Listing/schema +msgid "Headline button text" +msgstr "" + +#. Default: "Height (px)" +#: components/Blocks/Listing/schema +msgid "Height (px)" +msgstr "" + +#. Default: "Hero Block" +#: components/Blocks/HeroBlock/schema +#: index +msgid "Hero Block" +msgstr "" + +#. Default: "Explanatory text or summary displayed below the title." +#: components/Blocks/HeroBlock/schema +msgid "Hero description description" +msgstr "" + +#. Default: "Main highlighted title." +#: components/Blocks/HeroBlock/schema +msgid "Hero title description" +msgstr "" + +#. Default: "Hide image" +#: components/Blocks/HeroBlock/schema +msgid "Hide image" +msgstr "" + +#. Default: "Highlight" +#: index +msgid "Highlight" +msgstr "" + +#. Default: "Highlighted item" +#: components/Blocks/HeroBlock/schema +msgid "Highlighted item" +msgstr "" + +#. Default: "Select a content item to pull its data automatically (title, description, image)" +#: components/Blocks/HeroBlock/schema +msgid "Highlighted item description" +msgstr "" + +#. Default: "Id" +#: components/Controlpanels/Themes/messages +msgid "Id" +msgstr "" + +#. Default: "Image" +#: components/Blocks/HeroBlock/schema +msgid "Image" +msgstr "" + +#. Default: "Image fit" +#: components/Blocks/HeroBlock/schema +msgid "Image fit" +msgstr "" + +#. Default: "\"Cover\" fills the whole available space, cropping the edges of the image if needed. \"Contain\" fits the entire image inside the space, which may leave empty bands at the sides." +#: components/Blocks/HeroBlock/schema +msgid "Image fit description" +msgstr "" + +#. Default: "Image size" +#: components/Blocks/MainImageBlock/schema +msgid "Image size" +msgstr "" + +#. Default: "Image size / empty space" +#: components/Blocks/HeroBlock/schema +msgid "Image size / empty space" +msgstr "" + +#. Default: "Controls the width reserved for the image (even when there is none), which in turn sets the space for the text." +#: components/Blocks/HeroBlock/schema +msgid "Image size description" +msgstr "" + +#. Default: "50% (half)" +#: components/Blocks/HeroBlock/schema +msgid "Image size half" +msgstr "" + +#. Default: "0% (text full width)" +#: components/Blocks/HeroBlock/schema +msgid "Image size none" +msgstr "" + +#. Default: "Item batch size" +#: components/Widgets/QuerystringWidget +msgid "Item batch size" +msgstr "" + +#. Default: "Items" +#: components/Blocks/Listing/messages +msgid "Items" +msgstr "" + +#. Default: "Last modified" +#: components/DocumentByLine/DocumentByLine +msgid "Last modified" +msgstr "" + +#. Default: "Layout" +#: components/Blocks/HeroBlock/schema +msgid "Layout" +msgstr "" + +#. Default: "Left bottom" +#: components/Blocks/Listing/schema +msgid "Left bottom" +msgstr "" + +#. Default: "Left center" +#: components/Blocks/Listing/schema +msgid "Left center" +msgstr "" + +#. Default: "Left top" +#: components/Blocks/Listing/schema +msgid "Left top" +msgstr "" + +#. Default: "Link settings" +#: components/Blocks/MainImageBlock/schema +msgid "Link settings" +msgstr "" + +#. Default: "Link to" +#: components/Blocks/MainImageBlock/schema +msgid "Link to" +msgstr "" + +#. Default: "Main Image" +#: components/Blocks/MainImageBlock/schema +#: index +msgid "Main Image" +msgstr "" + +#. Default: "Media Carousel" +#: components/Blocks/Listing/messages +#: index +msgid "Media Carousel" +msgstr "" + +#. Default: "Media fit (object-fit)" +#: components/Blocks/Listing/schema +msgid "Media fit (object-fit)" +msgstr "" + +#. Default: "Name" +#: components/Controlpanels/Themes/messages +msgid "Name" +msgstr "" + +#. Default: "Next" +#: components/Blocks/Listing/messages +msgid "Next" +msgstr "" + +#. Default: "Next Page" +#: components/Blocks/Listing/ListingBody +msgid "Next Page" +msgstr "" + +#. Default: "No items found in this container." +#: components/Blocks/Listing/ListingBody +msgid "No items found in this container." +msgstr "" + +#. Default: "No preview image found on this page. Add a preview image to the page to display it in this block." +#: components/Blocks/MainImageBlock/View +msgid "No preview image found on this page." +msgstr "" + +#. Default: "No selection" +#: components/Widgets/QuerystringWidget +msgid "No selection" +msgstr "" + +#. Default: "Number of columns" +#: components/Blocks/Listing/schema +msgid "Number of columns" +msgstr "" + +#. Default: "Offset" +#: components/Widgets/QuerystringWidget +msgid "Offset" +msgstr "" + +#. Default: "Open in a new tab" +#: components/Blocks/MainImageBlock/schema +msgid "Open in a new tab" +msgstr "" + +#. Default: "Open menu" +#: components/Navigation/SCNavigation/MenuItem +msgid "Open menu" +msgstr "" + +#. Default: "Overwrite content" +#: components/Blocks/HeroBlock/schema +msgid "Overwrite content" +msgstr "" + +#. Default: "Check to customise the title, description and image instead of pulling them automatically from the highlighted item" +#: components/Blocks/HeroBlock/schema +msgid "Overwrite content description" +msgstr "" + +#. Default: "Pagination Navigation" +#: components/Blocks/Listing/ListingBody +msgid "Pagination Navigation" +msgstr "" + +#. Default: "Pagination Navigation for {headline}" +#: components/Blocks/Listing/ListingBody +msgid "Pagination Navigation for {headline}" +msgstr "" + +#. Default: "Paragraph (P)" +#: components/Blocks/HeroBlock/schema +msgid "Paragraph (P)" +msgstr "" + +#. Default: "Pause carousel" +#: components/Blocks/Listing/messages +msgid "Pause carousel" +msgstr "" + +#. Default: "Play carousel" +#: components/Blocks/Listing/messages +msgid "Play carousel" +msgstr "" + +#. Default: "Position (object-position)" +#: components/Blocks/Listing/schema +msgid "Position (object-position)" +msgstr "" + +#. Default: "Previous" +#: components/Blocks/Listing/messages +msgid "Previous" +msgstr "" + +#. Default: "Previous Page" +#: components/Blocks/Listing/ListingBody +msgid "Previous Page" +msgstr "" + +#. Default: "Primary" +#: index +msgid "Primary" +msgstr "" + +#. Default: "Published" +#: components/DocumentByLine/DocumentByLine +msgid "Published" +msgstr "" + +#. Default: "Quote" +#: components/Blocks/QuoteBlock/schema +#: index +msgid "Quote" +msgstr "" + +#. Default: "Result" +#: components/Blocks/Listing/ListingBody +msgid "Result" +msgstr "" + +#. Default: "Results limit" +#: components/Widgets/QuerystringWidget +msgid "Results limit" +msgstr "" + +#. Default: "Reversed order" +#: components/Widgets/QuerystringWidget +msgid "Reversed order" +msgstr "" + +#. Default: "Right bottom" +#: components/Blocks/Listing/schema +msgid "Right bottom" +msgstr "" + +#. Default: "Right center" +#: components/Blocks/Listing/schema +msgid "Right center" +msgstr "" + +#. Default: "Right top" +#: components/Blocks/Listing/schema +msgid "Right top" +msgstr "" + +#. Default: "Save" +#: components/Controlpanels/Themes/messages +msgid "Save" +msgstr "" + +#. Default: "Section" +#: components/Navigation/SCNavigation/SubMenuSection +msgid "Section" +msgstr "" + +#. Default: "Select a highlighted item" +#: components/Blocks/HeroBlock/useHeroBlockContent +msgid "Select a highlighted item" +msgstr "" + +#. Default: "Show author" +#: components/Blocks/DocumentByLine/schema +msgid "Show author" +msgstr "" + +#. Default: "Show button" +#: components/Blocks/HeroBlock/schema +msgid "Show button" +msgstr "" + +#. Default: "Show date" +#: components/Blocks/HeroBlock/schema +msgid "Show date" +msgstr "" + +#. Default: "Show last modification date" +#: components/Blocks/DocumentByLine/schema +msgid "Show last modification date" +msgstr "" + +#. Default: "Show publication date" +#: components/Blocks/DocumentByLine/schema +msgid "Show publication date" +msgstr "" + +#. Default: "Sort on" +#: components/Widgets/QuerystringWidget +msgid "Sort on" +msgstr "" + +#. Default: "Styling" +#: components/Blocks/MainImageBlock/schema +msgid "Styling" +msgstr "" + +#. Default: "Tags" +#: components/Blocks/HeroBlock/schema +msgid "Tags" +msgstr "" + +#. Default: "Text side" +#: components/Blocks/HeroBlock/schema +msgid "Text side" +msgstr "" + +#. Default: "Theme deleted" +#: components/Controlpanels/Themes/messages +msgid "Theme deleted" +msgstr "" + +#. Default: "Themes" +#: components/Controlpanels/Themes/messages +msgid "Themes" +msgstr "" + +#. Default: "Title" +#: components/Blocks/HeroBlock/schema +#: components/Blocks/MainImageBlock/schema +msgid "Title" +msgstr "" + +#. Default: "Title element" +#: components/Blocks/HeroBlock/schema +msgid "Title element" +msgstr "" + +#. Default: "H1 is the main page title, H2 the secondary title and H3 the tertiary one. P renders the title as a paragraph." +#: components/Blocks/HeroBlock/schema +msgid "Title element description" +msgstr "" + +#. Default: "Transparent" +#: components/Blocks/QuoteBlock/schema +msgid "Transparent" +msgstr "" + +#. Default: "Video" +#: components/Blocks/Listing/messages +msgid "Video" +msgstr "" + +#. Default: "Decrease font size" +#: components/AccessibilityControls/FontSizeControls +msgid "accessibilityDecreaseFontSize" +msgstr "" + +#. Default: "Read on hover" +#: components/AccessibilityControls/HoverReaderControls +msgid "accessibilityHoverReaderLabel" +msgstr "" + +#. Default: "Increase font size" +#: components/AccessibilityControls/FontSizeControls +msgid "accessibilityIncreaseFontSize" +msgstr "" + +#. Default: "Reader speed" +#: components/AccessibilityControls/HoverReaderControls +msgid "accessibilitySpeedLabel" +msgstr "" + +#. Default: "Dark" +#: components/ThemeToggle/ThemeToggle +msgid "darkTheme" +msgstr "" + +#. Default: "Toggle menu" +#: components/DropdownMenu/DropdownMenu +msgid "dropdownMenuToggle" +msgstr "" + +#. Default: "Accessibility menu" +#: components/HeaderBar/HeaderBar +msgid "headerBarAccessibilityMenu" +msgstr "" + +#. Default: "MENU" +#: components/HeaderBar/HeaderBar +msgid "headerBarMenuLabel" +msgstr "" + +#. Default: "High contrast" +#: components/ThemeToggle/ThemeToggle +msgid "highContrastTheme" +msgstr "" + +#. Default: "Light" +#: components/ThemeToggle/ThemeToggle +msgid "lightTheme" +msgstr "" + +#. Default: "Loading" +#: components/Blocks/Listing/ListingBody +msgid "loading" +msgstr "" + +#. Default: "of" +#: components/Blocks/Listing/ListingBody +msgid "of" +msgstr "" + +#. Default: "Toggle theme" +#: components/ThemeToggle/ThemeToggle +msgid "toggleTheme" +msgstr "" + +#. Default: "{count, plural, one {# column} other {# columns}}" +#: components/Blocks/Listing/schema +msgid "{count, plural, one {# column} other {# columns}}" +msgstr "" diff --git a/frontend/packages/volto-light-theme/locales/pt_BR/LC_MESSAGES/volto.po b/frontend/packages/volto-light-theme/locales/pt_BR/LC_MESSAGES/volto.po index e01604d..83e5667 100644 --- a/frontend/packages/volto-light-theme/locales/pt_BR/LC_MESSAGES/volto.po +++ b/frontend/packages/volto-light-theme/locales/pt_BR/LC_MESSAGES/volto.po @@ -5,7 +5,7 @@ msgstr "" "POT-Creation-Date: 2023-04-06T16:01:32.969Z\n" "PO-Revision-Date: \n" "Last-Translator: Plone i18n \n" -"Language: \n" +"Language: pt_BR\n" "Language-Team: Plone i18n \n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -15,3 +15,749 @@ msgstr "" "Language-Name: Português do Brasil\n" "Preferred-Encodings: utf-8\n" "Domain: volto\n" + +#. Default: "Accessibility options" +#: components/Header/MobileHeader/MobileTools +msgid "Accessibility options" +msgstr "Opções de acessibilidade" + +#. Default: "Actions" +#: components/Controlpanels/Themes/messages +msgid "Actions" +msgstr "Ações" + +#. Default: "Add theme" +#: components/Controlpanels/Themes/messages +msgid "Add theme" +msgstr "Adicionar tema" + +#. Default: "Alignment" +#: components/Blocks/Listing/schema +#: components/Blocks/MainImageBlock/schema +msgid "Alignment" +msgstr "Alinhamento" + +#. Default: "Alt Text" +#: components/Blocks/MainImageBlock/schema +msgid "Alt Text" +msgstr "Texto alternativo" + +#. Default: "Leave empty if the image is purely decorative" +#: components/Blocks/MainImageBlock/schema +msgid "Alt text hint" +msgstr "Deixe em branco se a imagem for puramente decorativa" + +#. Default: "Describe the purpose of the image" +#: components/Blocks/MainImageBlock/schema +msgid "Alt text hint link text" +msgstr "Descreva o propósito da imagem" + +#. Default: "Attached Card" +#: index +msgid "Attached Card" +msgstr "Card colado" + +#. Default: "Author / Source" +#: components/Blocks/QuoteBlock/schema +msgid "Author / Source" +msgstr "Autor / Fonte" + +#. Default: "Auto-advance" +#: components/Blocks/Listing/schema +msgid "Auto-advance" +msgstr "Auto-avanço" + +#. Default: "Auto-advance interval (ms)" +#: components/Blocks/Listing/schema +msgid "Auto-advance interval (ms)" +msgstr "Intervalo do auto-avanço (ms)" + +#. Default: "Back" +#: components/Controlpanels/Themes/messages +msgid "Back" +msgstr "Voltar" + +#. Default: "Background color" +#: components/Blocks/schema +msgid "Background color" +msgstr "Cor de fundo" + +#. Default: "Background style" +#: components/Blocks/QuoteBlock/schema +msgid "Background style" +msgstr "Estilo do fundo" + +#. Default: "Block Width" +#: components/Blocks/HeroBlock/schema +#: components/Blocks/MainImageBlock/schema +msgid "Block Width" +msgstr "Largura do bloco" + +#. Default: "Brand" +#: index +msgid "Brand" +msgstr "Marca" + +#. Default: "Button link" +#: components/Blocks/HeroBlock/schema +msgid "Button link" +msgstr "Link do botão" + +#. Default: "Button text" +#: components/Blocks/HeroBlock/schema +msgid "Button text" +msgstr "Texto do botão" + +#. Default: "The text shown inside the button (e.g. \"Read more\")." +#: components/Blocks/HeroBlock/schema +msgid "Button text description" +msgstr "O texto exibido dentro do botão (ex.: \"Saiba mais\")." + +#. Default: "By" +#: components/DocumentByLine/DocumentByLine +msgid "By" +msgstr "Por" + +#. Default: "Byline" +#: components/Blocks/DocumentByLine/schema +#: index +msgid "Byline" +msgstr "Assinatura" + +#. Default: "Call to Action" +#: components/Blocks/HeroBlock/schema +msgid "Call to Action" +msgstr "Chamada para ação" + +#. Default: "Cancel" +#: components/Controlpanels/Themes/messages +msgid "Cancel" +msgstr "Cancelar" + +#. Default: "Carousel" +#: components/Blocks/Listing/messages +#: components/Blocks/Listing/schema +#: index +msgid "Carousel" +msgstr "Carrossel" + +#. Default: "Carousel controls" +#: components/Blocks/Listing/messages +msgid "Carousel controls" +msgstr "Controles do carrossel" + +#. Default: "Center" +#: components/Blocks/Listing/schema +msgid "Center" +msgstr "Centro" + +#. Default: "Center bottom" +#: components/Blocks/Listing/schema +msgid "Center bottom" +msgstr "Fundo centro" + +#. Default: "Center top" +#: components/Blocks/Listing/schema +msgid "Center top" +msgstr "Topo centro" + +#. Default: "Changes saved" +#: components/Controlpanels/Themes/messages +msgid "Changes saved" +msgstr "Alterações salvas" + +#. Default: "Close menu" +#: components/Navigation/SCNavigation/SubMenu +msgid "Close menu" +msgstr "Fechar menu" + +#. Default: "Colours" +#: components/Controlpanels/Themes/messages +msgid "Colours" +msgstr "Cores" + +#. Default: "Contain" +#: components/Blocks/HeroBlock/schema +msgid "Contain" +msgstr "Conter" + +#. Default: "Content" +#: components/Blocks/HeroBlock/schema +msgid "Content" +msgstr "Conteúdo" + +#. Default: "Cover" +#: components/Blocks/HeroBlock/schema +msgid "Cover" +msgstr "Preencher" + +#. Default: "Criteria" +#: components/Widgets/QuerystringWidget +msgid "Criteria" +msgstr "Critérios" + +#. Default: "Delete" +#: components/Controlpanels/Themes/messages +msgid "Delete" +msgstr "Excluir" + +#. Default: "Delete this theme? Content still using it keeps the stored id but falls back to the default colours." +#: components/Controlpanels/Themes/messages +msgid "Delete this theme?" +msgstr "Excluir este tema? Conteúdos que ainda o utilizam mantêm o id armazenado, mas voltam às cores padrão." + +#. Default: "Description" +#: components/Blocks/HeroBlock/schema +#: components/Blocks/MainImageBlock/schema +msgid "Description" +msgstr "Descrição" + +#. Default: "Duplicate" +#: components/Controlpanels/Themes/messages +msgid "Duplicate" +msgstr "Duplicar" + +#. Default: "Duplicate of {name}" +#: components/Controlpanels/Themes/messages +msgid "Duplicate of {name}" +msgstr "Cópia de {name}" + +#. Default: "Edit" +#: components/Controlpanels/Themes/messages +msgid "Edit" +msgstr "Editar" + +#. Default: "This is a Hero Block. Add an item in the sidebar to fill it automatically." +#: components/Blocks/HeroBlock/useHeroBlockContent +msgid "Empty hero block placeholder" +msgstr "Este é um Hero Block. Adicione um item no menu lateral para preenchê-lo automaticamente." + +#. Default: "Error" +#: components/Controlpanels/Themes/messages +msgid "Error" +msgstr "Erro" + +#. Default: "File type" +#: components/Blocks/HeroBlock/schema +msgid "File type" +msgstr "Tipo do arquivo" + +#. Default: "E.g. PDF, Report, Article" +#: components/Blocks/HeroBlock/schema +msgid "File type description" +msgstr "Ex.: PDF, Relatório, Artigo" + +#. Default: "Filled" +#: components/Blocks/QuoteBlock/schema +msgid "Filled" +msgstr "Preenchido" + +#. Default: "Flex / Decorative" +#: index +msgid "Flex / Decorative" +msgstr "Flex / Decorativa" + +#. Default: "Footer text" +#: components/Blocks/HeroBlock/schema +msgid "Footer text" +msgstr "Texto do rodapé" + +#. Default: "Additional information displayed at the foot of the block." +#: components/Blocks/HeroBlock/schema +msgid "Footer text description" +msgstr "Informação adicional exibida no rodapé do bloco." + +#. Default: "Full width" +#: components/Blocks/HeroBlock/schema +msgid "Full width" +msgstr "Largura total" + +#. Default: "Let the text span the whole block when there is no image to show." +#: components/Blocks/HeroBlock/schema +msgid "Full width description" +msgstr "Permite que o texto ocupe todo o bloco quando não há imagem a exibir." + +#. Default: "Go to item {index}" +#: components/Blocks/Listing/messages +msgid "Go to item {index}" +msgstr "Ir para o item {index}" + +#. Default: "Header text" +#: components/Blocks/HeroBlock/schema +msgid "Header text" +msgstr "Texto do cabeçalho" + +#. Default: "Small text displayed above the main title." +#: components/Blocks/HeroBlock/schema +msgid "Header text description" +msgstr "Texto pequeno exibido acima do título principal." + +#. Default: "Headline button link" +#: components/Blocks/Listing/schema +msgid "Headline button link" +msgstr "Link do botão (título)" + +#. Default: "Headline button text" +#: components/Blocks/Listing/schema +msgid "Headline button text" +msgstr "Texto do botão (título)" + +#. Default: "Height (px)" +#: components/Blocks/Listing/schema +msgid "Height (px)" +msgstr "Altura (px)" + +#. Default: "Hero Block" +#: components/Blocks/HeroBlock/schema +#: index +msgid "Hero Block" +msgstr "Hero Block" + +#. Default: "Explanatory text or summary displayed below the title." +#: components/Blocks/HeroBlock/schema +msgid "Hero description description" +msgstr "Texto explicativo ou resumo exibido abaixo do título." + +#. Default: "Main highlighted title." +#: components/Blocks/HeroBlock/schema +msgid "Hero title description" +msgstr "Título principal em destaque." + +#. Default: "Hide image" +#: components/Blocks/HeroBlock/schema +msgid "Hide image" +msgstr "Esconder imagem" + +#. Default: "Highlight" +#: index +msgid "Highlight" +msgstr "Destaque" + +#. Default: "Highlighted item" +#: components/Blocks/HeroBlock/schema +msgid "Highlighted item" +msgstr "Item de destaque" + +#. Default: "Select a content item to pull its data automatically (title, description, image)" +#: components/Blocks/HeroBlock/schema +msgid "Highlighted item description" +msgstr "Selecione um conteúdo para puxar os dados automaticamente (título, descrição, imagem)" + +#. Default: "Id" +#: components/Controlpanels/Themes/messages +msgid "Id" +msgstr "Id" + +#. Default: "Image" +#: components/Blocks/HeroBlock/schema +msgid "Image" +msgstr "Imagem" + +#. Default: "Image fit" +#: components/Blocks/HeroBlock/schema +msgid "Image fit" +msgstr "Ajuste da imagem" + +#. Default: "\"Cover\" fills the whole available space, cropping the edges of the image if needed. \"Contain\" fits the entire image inside the space, which may leave empty bands at the sides." +#: components/Blocks/HeroBlock/schema +msgid "Image fit description" +msgstr "O \"Preencher\" ocupa todo o espaço disponível, cortando as bordas da imagem se necessário. O \"Conter\" ajusta a imagem inteira dentro do espaço, podendo deixar faixas laterais vazias." + +#. Default: "Image size" +#: components/Blocks/MainImageBlock/schema +msgid "Image size" +msgstr "Tamanho da imagem" + +#. Default: "Image size / empty space" +#: components/Blocks/HeroBlock/schema +msgid "Image size / empty space" +msgstr "Tamanho da imagem / espaço vazio" + +#. Default: "Controls the width reserved for the image (even when there is none), which in turn sets the space for the text." +#: components/Blocks/HeroBlock/schema +msgid "Image size description" +msgstr "Controla a largura reservada para a imagem (mesmo quando não há uma), definindo o espaço do texto." + +#. Default: "50% (half)" +#: components/Blocks/HeroBlock/schema +msgid "Image size half" +msgstr "50% (metade)" + +#. Default: "0% (text full width)" +#: components/Blocks/HeroBlock/schema +msgid "Image size none" +msgstr "0% (texto 100%)" + +#. Default: "Item batch size" +#: components/Widgets/QuerystringWidget +msgid "Item batch size" +msgstr "Itens por página" + +#. Default: "Items" +#: components/Blocks/Listing/messages +msgid "Items" +msgstr "Itens" + +#. Default: "Last modified" +#: components/DocumentByLine/DocumentByLine +msgid "Last modified" +msgstr "Última modificação" + +#. Default: "Layout" +#: components/Blocks/HeroBlock/schema +msgid "Layout" +msgstr "Layout" + +#. Default: "Left bottom" +#: components/Blocks/Listing/schema +msgid "Left bottom" +msgstr "Fundo esquerdo" + +#. Default: "Left center" +#: components/Blocks/Listing/schema +msgid "Left center" +msgstr "Centro esquerdo" + +#. Default: "Left top" +#: components/Blocks/Listing/schema +msgid "Left top" +msgstr "Topo esquerdo" + +#. Default: "Link settings" +#: components/Blocks/MainImageBlock/schema +msgid "Link settings" +msgstr "Configurações do link" + +#. Default: "Link to" +#: components/Blocks/MainImageBlock/schema +msgid "Link to" +msgstr "Link para" + +#. Default: "Main Image" +#: components/Blocks/MainImageBlock/schema +#: index +msgid "Main Image" +msgstr "Imagem principal" + +#. Default: "Media Carousel" +#: components/Blocks/Listing/messages +#: index +msgid "Media Carousel" +msgstr "Carrossel de mídia" + +#. Default: "Media fit (object-fit)" +#: components/Blocks/Listing/schema +msgid "Media fit (object-fit)" +msgstr "Ajuste da mídia (object-fit)" + +#. Default: "Name" +#: components/Controlpanels/Themes/messages +msgid "Name" +msgstr "Nome" + +#. Default: "Next" +#: components/Blocks/Listing/messages +msgid "Next" +msgstr "Próximo" + +#. Default: "Next Page" +#: components/Blocks/Listing/ListingBody +msgid "Next Page" +msgstr "Próxima página" + +#. Default: "No items found in this container." +#: components/Blocks/Listing/ListingBody +msgid "No items found in this container." +msgstr "Nenhum item encontrado neste contêiner." + +#. Default: "No preview image found on this page. Add a preview image to the page to display it in this block." +#: components/Blocks/MainImageBlock/View +msgid "No preview image found on this page." +msgstr "Nenhuma imagem de preview encontrada nesta página. Adicione uma imagem de preview à página para exibi-la neste bloco." + +#. Default: "No selection" +#: components/Widgets/QuerystringWidget +msgid "No selection" +msgstr "Nenhuma seleção" + +#. Default: "Number of columns" +#: components/Blocks/Listing/schema +msgid "Number of columns" +msgstr "Número de colunas" + +#. Default: "Offset" +#: components/Widgets/QuerystringWidget +msgid "Offset" +msgstr "Deslocamento" + +#. Default: "Open in a new tab" +#: components/Blocks/MainImageBlock/schema +msgid "Open in a new tab" +msgstr "Abrir em uma nova aba" + +#. Default: "Open menu" +#: components/Navigation/SCNavigation/MenuItem +msgid "Open menu" +msgstr "Abrir menu" + +#. Default: "Overwrite content" +#: components/Blocks/HeroBlock/schema +msgid "Overwrite content" +msgstr "Sobrescrever conteúdo" + +#. Default: "Check to customise the title, description and image instead of pulling them automatically from the highlighted item" +#: components/Blocks/HeroBlock/schema +msgid "Overwrite content description" +msgstr "Marque para personalizar o título, a descrição e a imagem em vez de puxá-los automaticamente do item de destaque" + +#. Default: "Pagination Navigation" +#: components/Blocks/Listing/ListingBody +msgid "Pagination Navigation" +msgstr "Navegação de páginas" + +#. Default: "Pagination Navigation for {headline}" +#: components/Blocks/Listing/ListingBody +msgid "Pagination Navigation for {headline}" +msgstr "Navegação de páginas de {headline}" + +#. Default: "Paragraph (P)" +#: components/Blocks/HeroBlock/schema +msgid "Paragraph (P)" +msgstr "Parágrafo (P)" + +#. Default: "Pause carousel" +#: components/Blocks/Listing/messages +msgid "Pause carousel" +msgstr "Pausar carrossel" + +#. Default: "Play carousel" +#: components/Blocks/Listing/messages +msgid "Play carousel" +msgstr "Reproduzir carrossel" + +#. Default: "Position (object-position)" +#: components/Blocks/Listing/schema +msgid "Position (object-position)" +msgstr "Posição (object-position)" + +#. Default: "Previous" +#: components/Blocks/Listing/messages +msgid "Previous" +msgstr "Anterior" + +#. Default: "Previous Page" +#: components/Blocks/Listing/ListingBody +msgid "Previous Page" +msgstr "Página anterior" + +#. Default: "Primary" +#: index +msgid "Primary" +msgstr "Primária" + +#. Default: "Published" +#: components/DocumentByLine/DocumentByLine +msgid "Published" +msgstr "Publicado" + +#. Default: "Quote" +#: components/Blocks/QuoteBlock/schema +#: index +msgid "Quote" +msgstr "Citação" + +#. Default: "Result" +#: components/Blocks/Listing/ListingBody +msgid "Result" +msgstr "Resultado" + +#. Default: "Results limit" +#: components/Widgets/QuerystringWidget +msgid "Results limit" +msgstr "Limite de resultados" + +#. Default: "Reversed order" +#: components/Widgets/QuerystringWidget +msgid "Reversed order" +msgstr "Ordem invertida" + +#. Default: "Right bottom" +#: components/Blocks/Listing/schema +msgid "Right bottom" +msgstr "Fundo direito" + +#. Default: "Right center" +#: components/Blocks/Listing/schema +msgid "Right center" +msgstr "Centro direito" + +#. Default: "Right top" +#: components/Blocks/Listing/schema +msgid "Right top" +msgstr "Topo direito" + +#. Default: "Save" +#: components/Controlpanels/Themes/messages +msgid "Save" +msgstr "Salvar" + +#. Default: "Section" +#: components/Navigation/SCNavigation/SubMenuSection +msgid "Section" +msgstr "Seção" + +#. Default: "Select a highlighted item" +#: components/Blocks/HeroBlock/useHeroBlockContent +msgid "Select a highlighted item" +msgstr "Selecione um item de destaque" + +#. Default: "Show author" +#: components/Blocks/DocumentByLine/schema +msgid "Show author" +msgstr "Exibir autor" + +#. Default: "Show button" +#: components/Blocks/HeroBlock/schema +msgid "Show button" +msgstr "Mostrar botão" + +#. Default: "Show date" +#: components/Blocks/HeroBlock/schema +msgid "Show date" +msgstr "Mostrar data" + +#. Default: "Show last modification date" +#: components/Blocks/DocumentByLine/schema +msgid "Show last modification date" +msgstr "Exibir data da última modificação" + +#. Default: "Show publication date" +#: components/Blocks/DocumentByLine/schema +msgid "Show publication date" +msgstr "Exibir data de publicação" + +#. Default: "Sort on" +#: components/Widgets/QuerystringWidget +msgid "Sort on" +msgstr "Ordenar por" + +#. Default: "Styling" +#: components/Blocks/MainImageBlock/schema +msgid "Styling" +msgstr "Estilo" + +#. Default: "Tags" +#: components/Blocks/HeroBlock/schema +msgid "Tags" +msgstr "Tags" + +#. Default: "Text side" +#: components/Blocks/HeroBlock/schema +msgid "Text side" +msgstr "Lado do texto" + +#. Default: "Theme deleted" +#: components/Controlpanels/Themes/messages +msgid "Theme deleted" +msgstr "Tema excluído" + +#. Default: "Themes" +#: components/Controlpanels/Themes/messages +msgid "Themes" +msgstr "Temas" + +#. Default: "Title" +#: components/Blocks/HeroBlock/schema +#: components/Blocks/MainImageBlock/schema +msgid "Title" +msgstr "Título" + +#. Default: "Title element" +#: components/Blocks/HeroBlock/schema +msgid "Title element" +msgstr "Elemento do título" + +#. Default: "H1 is the main page title, H2 the secondary title and H3 the tertiary one. P renders the title as a paragraph." +#: components/Blocks/HeroBlock/schema +msgid "Title element description" +msgstr "O H1 é o título principal da página, o H2 o secundário e o H3 o terciário. O P exibe o título como parágrafo." + +#. Default: "Transparent" +#: components/Blocks/QuoteBlock/schema +msgid "Transparent" +msgstr "Transparente" + +#. Default: "Video" +#: components/Blocks/Listing/messages +msgid "Video" +msgstr "Vídeo" + +#. Default: "Decrease font size" +#: components/AccessibilityControls/FontSizeControls +msgid "accessibilityDecreaseFontSize" +msgstr "Diminuir fonte" + +#. Default: "Read on hover" +#: components/AccessibilityControls/HoverReaderControls +msgid "accessibilityHoverReaderLabel" +msgstr "Leitura ao passar o mouse" + +#. Default: "Increase font size" +#: components/AccessibilityControls/FontSizeControls +msgid "accessibilityIncreaseFontSize" +msgstr "Aumentar fonte" + +#. Default: "Reader speed" +#: components/AccessibilityControls/HoverReaderControls +msgid "accessibilitySpeedLabel" +msgstr "Velocidade do leitor" + +#. Default: "Dark" +#: components/ThemeToggle/ThemeToggle +msgid "darkTheme" +msgstr "Escuro" + +#. Default: "Toggle menu" +#: components/DropdownMenu/DropdownMenu +msgid "dropdownMenuToggle" +msgstr "Alternar menu" + +#. Default: "Accessibility menu" +#: components/HeaderBar/HeaderBar +msgid "headerBarAccessibilityMenu" +msgstr "Menu de acessibilidade" + +#. Default: "MENU" +#: components/HeaderBar/HeaderBar +msgid "headerBarMenuLabel" +msgstr "MENU" + +#. Default: "High contrast" +#: components/ThemeToggle/ThemeToggle +msgid "highContrastTheme" +msgstr "Alto contraste" + +#. Default: "Light" +#: components/ThemeToggle/ThemeToggle +msgid "lightTheme" +msgstr "Claro" + +#. Default: "Loading" +#: components/Blocks/Listing/ListingBody +msgid "loading" +msgstr "Carregando" + +#. Default: "of" +#: components/Blocks/Listing/ListingBody +msgid "of" +msgstr "de" + +#. Default: "Toggle theme" +#: components/ThemeToggle/ThemeToggle +msgid "toggleTheme" +msgstr "Alternar tema" + +#. Default: "{count, plural, one {# column} other {# columns}}" +#: components/Blocks/Listing/schema +msgid "{count, plural, one {# column} other {# columns}}" +msgstr "{count, plural, one {# coluna} other {# colunas}}" diff --git a/frontend/packages/volto-light-theme/locales/volto.pot b/frontend/packages/volto-light-theme/locales/volto.pot index ea538b1..5b0fcca 100644 --- a/frontend/packages/volto-light-theme/locales/volto.pot +++ b/frontend/packages/volto-light-theme/locales/volto.pot @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Plone\n" -"POT-Creation-Date: 2024-03-22T12:43:34.158Z\n" +"POT-Creation-Date: 2026-09-01T23:28:00.430Z\n" "Last-Translator: Plone i18n \n" "Language-Team: Plone i18n \n" "Content-Type: text/plain; charset=utf-8\n" @@ -12,3 +12,749 @@ msgstr "" "Language-Name: English\n" "Preferred-Encodings: utf-8\n" "Domain: volto\n" + +#. Default: "Accessibility options" +#: components/Header/MobileHeader/MobileTools +msgid "Accessibility options" +msgstr "" + +#. Default: "Actions" +#: components/Controlpanels/Themes/messages +msgid "Actions" +msgstr "" + +#. Default: "Add theme" +#: components/Controlpanels/Themes/messages +msgid "Add theme" +msgstr "" + +#. Default: "Alignment" +#: components/Blocks/Listing/schema +#: components/Blocks/MainImageBlock/schema +msgid "Alignment" +msgstr "" + +#. Default: "Alt Text" +#: components/Blocks/MainImageBlock/schema +msgid "Alt Text" +msgstr "" + +#. Default: "Leave empty if the image is purely decorative" +#: components/Blocks/MainImageBlock/schema +msgid "Alt text hint" +msgstr "" + +#. Default: "Describe the purpose of the image" +#: components/Blocks/MainImageBlock/schema +msgid "Alt text hint link text" +msgstr "" + +#. Default: "Attached Card" +#: index +msgid "Attached Card" +msgstr "" + +#. Default: "Author / Source" +#: components/Blocks/QuoteBlock/schema +msgid "Author / Source" +msgstr "" + +#. Default: "Auto-advance" +#: components/Blocks/Listing/schema +msgid "Auto-advance" +msgstr "" + +#. Default: "Auto-advance interval (ms)" +#: components/Blocks/Listing/schema +msgid "Auto-advance interval (ms)" +msgstr "" + +#. Default: "Back" +#: components/Controlpanels/Themes/messages +msgid "Back" +msgstr "" + +#. Default: "Background color" +#: components/Blocks/schema +msgid "Background color" +msgstr "" + +#. Default: "Background style" +#: components/Blocks/QuoteBlock/schema +msgid "Background style" +msgstr "" + +#. Default: "Block Width" +#: components/Blocks/HeroBlock/schema +#: components/Blocks/MainImageBlock/schema +msgid "Block Width" +msgstr "" + +#. Default: "Brand" +#: index +msgid "Brand" +msgstr "" + +#. Default: "Button link" +#: components/Blocks/HeroBlock/schema +msgid "Button link" +msgstr "" + +#. Default: "Button text" +#: components/Blocks/HeroBlock/schema +msgid "Button text" +msgstr "" + +#. Default: "The text shown inside the button (e.g. \"Read more\")." +#: components/Blocks/HeroBlock/schema +msgid "Button text description" +msgstr "" + +#. Default: "By" +#: components/DocumentByLine/DocumentByLine +msgid "By" +msgstr "" + +#. Default: "Byline" +#: components/Blocks/DocumentByLine/schema +#: index +msgid "Byline" +msgstr "" + +#. Default: "Call to Action" +#: components/Blocks/HeroBlock/schema +msgid "Call to Action" +msgstr "" + +#. Default: "Cancel" +#: components/Controlpanels/Themes/messages +msgid "Cancel" +msgstr "" + +#. Default: "Carousel" +#: components/Blocks/Listing/messages +#: components/Blocks/Listing/schema +#: index +msgid "Carousel" +msgstr "" + +#. Default: "Carousel controls" +#: components/Blocks/Listing/messages +msgid "Carousel controls" +msgstr "" + +#. Default: "Center" +#: components/Blocks/Listing/schema +msgid "Center" +msgstr "" + +#. Default: "Center bottom" +#: components/Blocks/Listing/schema +msgid "Center bottom" +msgstr "" + +#. Default: "Center top" +#: components/Blocks/Listing/schema +msgid "Center top" +msgstr "" + +#. Default: "Changes saved" +#: components/Controlpanels/Themes/messages +msgid "Changes saved" +msgstr "" + +#. Default: "Close menu" +#: components/Navigation/SCNavigation/SubMenu +msgid "Close menu" +msgstr "" + +#. Default: "Colours" +#: components/Controlpanels/Themes/messages +msgid "Colours" +msgstr "" + +#. Default: "Contain" +#: components/Blocks/HeroBlock/schema +msgid "Contain" +msgstr "" + +#. Default: "Content" +#: components/Blocks/HeroBlock/schema +msgid "Content" +msgstr "" + +#. Default: "Cover" +#: components/Blocks/HeroBlock/schema +msgid "Cover" +msgstr "" + +#. Default: "Criteria" +#: components/Widgets/QuerystringWidget +msgid "Criteria" +msgstr "" + +#. Default: "Delete" +#: components/Controlpanels/Themes/messages +msgid "Delete" +msgstr "" + +#. Default: "Delete this theme? Content still using it keeps the stored id but falls back to the default colours." +#: components/Controlpanels/Themes/messages +msgid "Delete this theme?" +msgstr "" + +#. Default: "Description" +#: components/Blocks/HeroBlock/schema +#: components/Blocks/MainImageBlock/schema +msgid "Description" +msgstr "" + +#. Default: "Duplicate" +#: components/Controlpanels/Themes/messages +msgid "Duplicate" +msgstr "" + +#. Default: "Duplicate of {name}" +#: components/Controlpanels/Themes/messages +msgid "Duplicate of {name}" +msgstr "" + +#. Default: "Edit" +#: components/Controlpanels/Themes/messages +msgid "Edit" +msgstr "" + +#. Default: "This is a Hero Block. Add an item in the sidebar to fill it automatically." +#: components/Blocks/HeroBlock/useHeroBlockContent +msgid "Empty hero block placeholder" +msgstr "" + +#. Default: "Error" +#: components/Controlpanels/Themes/messages +msgid "Error" +msgstr "" + +#. Default: "File type" +#: components/Blocks/HeroBlock/schema +msgid "File type" +msgstr "" + +#. Default: "E.g. PDF, Report, Article" +#: components/Blocks/HeroBlock/schema +msgid "File type description" +msgstr "" + +#. Default: "Filled" +#: components/Blocks/QuoteBlock/schema +msgid "Filled" +msgstr "" + +#. Default: "Flex / Decorative" +#: index +msgid "Flex / Decorative" +msgstr "" + +#. Default: "Footer text" +#: components/Blocks/HeroBlock/schema +msgid "Footer text" +msgstr "" + +#. Default: "Additional information displayed at the foot of the block." +#: components/Blocks/HeroBlock/schema +msgid "Footer text description" +msgstr "" + +#. Default: "Full width" +#: components/Blocks/HeroBlock/schema +msgid "Full width" +msgstr "" + +#. Default: "Let the text span the whole block when there is no image to show." +#: components/Blocks/HeroBlock/schema +msgid "Full width description" +msgstr "" + +#. Default: "Go to item {index}" +#: components/Blocks/Listing/messages +msgid "Go to item {index}" +msgstr "" + +#. Default: "Header text" +#: components/Blocks/HeroBlock/schema +msgid "Header text" +msgstr "" + +#. Default: "Small text displayed above the main title." +#: components/Blocks/HeroBlock/schema +msgid "Header text description" +msgstr "" + +#. Default: "Headline button link" +#: components/Blocks/Listing/schema +msgid "Headline button link" +msgstr "" + +#. Default: "Headline button text" +#: components/Blocks/Listing/schema +msgid "Headline button text" +msgstr "" + +#. Default: "Height (px)" +#: components/Blocks/Listing/schema +msgid "Height (px)" +msgstr "" + +#. Default: "Hero Block" +#: components/Blocks/HeroBlock/schema +#: index +msgid "Hero Block" +msgstr "" + +#. Default: "Explanatory text or summary displayed below the title." +#: components/Blocks/HeroBlock/schema +msgid "Hero description description" +msgstr "" + +#. Default: "Main highlighted title." +#: components/Blocks/HeroBlock/schema +msgid "Hero title description" +msgstr "" + +#. Default: "Hide image" +#: components/Blocks/HeroBlock/schema +msgid "Hide image" +msgstr "" + +#. Default: "Highlight" +#: index +msgid "Highlight" +msgstr "" + +#. Default: "Highlighted item" +#: components/Blocks/HeroBlock/schema +msgid "Highlighted item" +msgstr "" + +#. Default: "Select a content item to pull its data automatically (title, description, image)" +#: components/Blocks/HeroBlock/schema +msgid "Highlighted item description" +msgstr "" + +#. Default: "Id" +#: components/Controlpanels/Themes/messages +msgid "Id" +msgstr "" + +#. Default: "Image" +#: components/Blocks/HeroBlock/schema +msgid "Image" +msgstr "" + +#. Default: "Image fit" +#: components/Blocks/HeroBlock/schema +msgid "Image fit" +msgstr "" + +#. Default: "\"Cover\" fills the whole available space, cropping the edges of the image if needed. \"Contain\" fits the entire image inside the space, which may leave empty bands at the sides." +#: components/Blocks/HeroBlock/schema +msgid "Image fit description" +msgstr "" + +#. Default: "Image size" +#: components/Blocks/MainImageBlock/schema +msgid "Image size" +msgstr "" + +#. Default: "Image size / empty space" +#: components/Blocks/HeroBlock/schema +msgid "Image size / empty space" +msgstr "" + +#. Default: "Controls the width reserved for the image (even when there is none), which in turn sets the space for the text." +#: components/Blocks/HeroBlock/schema +msgid "Image size description" +msgstr "" + +#. Default: "50% (half)" +#: components/Blocks/HeroBlock/schema +msgid "Image size half" +msgstr "" + +#. Default: "0% (text full width)" +#: components/Blocks/HeroBlock/schema +msgid "Image size none" +msgstr "" + +#. Default: "Item batch size" +#: components/Widgets/QuerystringWidget +msgid "Item batch size" +msgstr "" + +#. Default: "Items" +#: components/Blocks/Listing/messages +msgid "Items" +msgstr "" + +#. Default: "Last modified" +#: components/DocumentByLine/DocumentByLine +msgid "Last modified" +msgstr "" + +#. Default: "Layout" +#: components/Blocks/HeroBlock/schema +msgid "Layout" +msgstr "" + +#. Default: "Left bottom" +#: components/Blocks/Listing/schema +msgid "Left bottom" +msgstr "" + +#. Default: "Left center" +#: components/Blocks/Listing/schema +msgid "Left center" +msgstr "" + +#. Default: "Left top" +#: components/Blocks/Listing/schema +msgid "Left top" +msgstr "" + +#. Default: "Link settings" +#: components/Blocks/MainImageBlock/schema +msgid "Link settings" +msgstr "" + +#. Default: "Link to" +#: components/Blocks/MainImageBlock/schema +msgid "Link to" +msgstr "" + +#. Default: "Main Image" +#: components/Blocks/MainImageBlock/schema +#: index +msgid "Main Image" +msgstr "" + +#. Default: "Media Carousel" +#: components/Blocks/Listing/messages +#: index +msgid "Media Carousel" +msgstr "" + +#. Default: "Media fit (object-fit)" +#: components/Blocks/Listing/schema +msgid "Media fit (object-fit)" +msgstr "" + +#. Default: "Name" +#: components/Controlpanels/Themes/messages +msgid "Name" +msgstr "" + +#. Default: "Next" +#: components/Blocks/Listing/messages +msgid "Next" +msgstr "" + +#. Default: "Next Page" +#: components/Blocks/Listing/ListingBody +msgid "Next Page" +msgstr "" + +#. Default: "No items found in this container." +#: components/Blocks/Listing/ListingBody +msgid "No items found in this container." +msgstr "" + +#. Default: "No preview image found on this page. Add a preview image to the page to display it in this block." +#: components/Blocks/MainImageBlock/View +msgid "No preview image found on this page." +msgstr "" + +#. Default: "No selection" +#: components/Widgets/QuerystringWidget +msgid "No selection" +msgstr "" + +#. Default: "Number of columns" +#: components/Blocks/Listing/schema +msgid "Number of columns" +msgstr "" + +#. Default: "Offset" +#: components/Widgets/QuerystringWidget +msgid "Offset" +msgstr "" + +#. Default: "Open in a new tab" +#: components/Blocks/MainImageBlock/schema +msgid "Open in a new tab" +msgstr "" + +#. Default: "Open menu" +#: components/Navigation/SCNavigation/MenuItem +msgid "Open menu" +msgstr "" + +#. Default: "Overwrite content" +#: components/Blocks/HeroBlock/schema +msgid "Overwrite content" +msgstr "" + +#. Default: "Check to customise the title, description and image instead of pulling them automatically from the highlighted item" +#: components/Blocks/HeroBlock/schema +msgid "Overwrite content description" +msgstr "" + +#. Default: "Pagination Navigation" +#: components/Blocks/Listing/ListingBody +msgid "Pagination Navigation" +msgstr "" + +#. Default: "Pagination Navigation for {headline}" +#: components/Blocks/Listing/ListingBody +msgid "Pagination Navigation for {headline}" +msgstr "" + +#. Default: "Paragraph (P)" +#: components/Blocks/HeroBlock/schema +msgid "Paragraph (P)" +msgstr "" + +#. Default: "Pause carousel" +#: components/Blocks/Listing/messages +msgid "Pause carousel" +msgstr "" + +#. Default: "Play carousel" +#: components/Blocks/Listing/messages +msgid "Play carousel" +msgstr "" + +#. Default: "Position (object-position)" +#: components/Blocks/Listing/schema +msgid "Position (object-position)" +msgstr "" + +#. Default: "Previous" +#: components/Blocks/Listing/messages +msgid "Previous" +msgstr "" + +#. Default: "Previous Page" +#: components/Blocks/Listing/ListingBody +msgid "Previous Page" +msgstr "" + +#. Default: "Primary" +#: index +msgid "Primary" +msgstr "" + +#. Default: "Published" +#: components/DocumentByLine/DocumentByLine +msgid "Published" +msgstr "" + +#. Default: "Quote" +#: components/Blocks/QuoteBlock/schema +#: index +msgid "Quote" +msgstr "" + +#. Default: "Result" +#: components/Blocks/Listing/ListingBody +msgid "Result" +msgstr "" + +#. Default: "Results limit" +#: components/Widgets/QuerystringWidget +msgid "Results limit" +msgstr "" + +#. Default: "Reversed order" +#: components/Widgets/QuerystringWidget +msgid "Reversed order" +msgstr "" + +#. Default: "Right bottom" +#: components/Blocks/Listing/schema +msgid "Right bottom" +msgstr "" + +#. Default: "Right center" +#: components/Blocks/Listing/schema +msgid "Right center" +msgstr "" + +#. Default: "Right top" +#: components/Blocks/Listing/schema +msgid "Right top" +msgstr "" + +#. Default: "Save" +#: components/Controlpanels/Themes/messages +msgid "Save" +msgstr "" + +#. Default: "Section" +#: components/Navigation/SCNavigation/SubMenuSection +msgid "Section" +msgstr "" + +#. Default: "Select a highlighted item" +#: components/Blocks/HeroBlock/useHeroBlockContent +msgid "Select a highlighted item" +msgstr "" + +#. Default: "Show author" +#: components/Blocks/DocumentByLine/schema +msgid "Show author" +msgstr "" + +#. Default: "Show button" +#: components/Blocks/HeroBlock/schema +msgid "Show button" +msgstr "" + +#. Default: "Show date" +#: components/Blocks/HeroBlock/schema +msgid "Show date" +msgstr "" + +#. Default: "Show last modification date" +#: components/Blocks/DocumentByLine/schema +msgid "Show last modification date" +msgstr "" + +#. Default: "Show publication date" +#: components/Blocks/DocumentByLine/schema +msgid "Show publication date" +msgstr "" + +#. Default: "Sort on" +#: components/Widgets/QuerystringWidget +msgid "Sort on" +msgstr "" + +#. Default: "Styling" +#: components/Blocks/MainImageBlock/schema +msgid "Styling" +msgstr "" + +#. Default: "Tags" +#: components/Blocks/HeroBlock/schema +msgid "Tags" +msgstr "" + +#. Default: "Text side" +#: components/Blocks/HeroBlock/schema +msgid "Text side" +msgstr "" + +#. Default: "Theme deleted" +#: components/Controlpanels/Themes/messages +msgid "Theme deleted" +msgstr "" + +#. Default: "Themes" +#: components/Controlpanels/Themes/messages +msgid "Themes" +msgstr "" + +#. Default: "Title" +#: components/Blocks/HeroBlock/schema +#: components/Blocks/MainImageBlock/schema +msgid "Title" +msgstr "" + +#. Default: "Title element" +#: components/Blocks/HeroBlock/schema +msgid "Title element" +msgstr "" + +#. Default: "H1 is the main page title, H2 the secondary title and H3 the tertiary one. P renders the title as a paragraph." +#: components/Blocks/HeroBlock/schema +msgid "Title element description" +msgstr "" + +#. Default: "Transparent" +#: components/Blocks/QuoteBlock/schema +msgid "Transparent" +msgstr "" + +#. Default: "Video" +#: components/Blocks/Listing/messages +msgid "Video" +msgstr "" + +#. Default: "Decrease font size" +#: components/AccessibilityControls/FontSizeControls +msgid "accessibilityDecreaseFontSize" +msgstr "" + +#. Default: "Read on hover" +#: components/AccessibilityControls/HoverReaderControls +msgid "accessibilityHoverReaderLabel" +msgstr "" + +#. Default: "Increase font size" +#: components/AccessibilityControls/FontSizeControls +msgid "accessibilityIncreaseFontSize" +msgstr "" + +#. Default: "Reader speed" +#: components/AccessibilityControls/HoverReaderControls +msgid "accessibilitySpeedLabel" +msgstr "" + +#. Default: "Dark" +#: components/ThemeToggle/ThemeToggle +msgid "darkTheme" +msgstr "" + +#. Default: "Toggle menu" +#: components/DropdownMenu/DropdownMenu +msgid "dropdownMenuToggle" +msgstr "" + +#. Default: "Accessibility menu" +#: components/HeaderBar/HeaderBar +msgid "headerBarAccessibilityMenu" +msgstr "" + +#. Default: "MENU" +#: components/HeaderBar/HeaderBar +msgid "headerBarMenuLabel" +msgstr "" + +#. Default: "High contrast" +#: components/ThemeToggle/ThemeToggle +msgid "highContrastTheme" +msgstr "" + +#. Default: "Light" +#: components/ThemeToggle/ThemeToggle +msgid "lightTheme" +msgstr "" + +#. Default: "Loading" +#: components/Blocks/Listing/ListingBody +msgid "loading" +msgstr "" + +#. Default: "of" +#: components/Blocks/Listing/ListingBody +msgid "of" +msgstr "" + +#. Default: "Toggle theme" +#: components/ThemeToggle/ThemeToggle +msgid "toggleTheme" +msgstr "" + +#. Default: "{count, plural, one {# column} other {# columns}}" +#: components/Blocks/Listing/schema +msgid "{count, plural, one {# column} other {# columns}}" +msgstr "" diff --git a/frontend/packages/volto-light-theme/news/22.bugfix b/frontend/packages/volto-light-theme/news/22.bugfix new file mode 100644 index 0000000..a787a8a --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.bugfix @@ -0,0 +1 @@ +Fixed keyboard focus being invisible on listing cards. Six `outline` declarations in `_listing.scss` read `--theme-top-high-contrast-foreground-color`, which no theme defined, so the whole declaration was invalid and dropped. Four of the fourteen properties mapped by `createThemeDefinition` had no value in either theme; all of them are now defined, which also restores the carousel's high-contrast foreground, gives the `brand` theme a valid `border-width` outside high-contrast mode, and makes the Pattern layer's tokens resolve. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.bugfix.10 b/frontend/packages/volto-light-theme/news/22.bugfix.10 new file mode 100644 index 0000000..f9534c9 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.bugfix.10 @@ -0,0 +1 @@ +Fixed the content-type colours doing nothing when set from the themes control panel. `_root.scss` read `--event-color-override`, `--file-color-override` and `--image-color-override`, which nothing writes: a theme's `event_color_light` setting reaches the page as `--event-color-light`. The three tokens now read the properties a theme actually sets, through `light-dark()` like every other colour, so they follow the colour mode as well. The contract between the backend schema and the stylesheet is now tested from both ends — the previous tests checked that a field derives a custom property, but not that any stylesheet reads it. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.bugfix.11 b/frontend/packages/volto-light-theme/news/22.bugfix.11 new file mode 100644 index 0000000..9cc0b92 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.bugfix.11 @@ -0,0 +1 @@ +Gave the `documentByline` block a stylesheet. It had none anywhere in `src/theme/`, so on a page it started flush against the viewport edge while every other block sat in the centred content column. The three fields are now inline, separated by a dash, at the small type step, with author names comma-separated; the byline also reads author first, then published, then modified, and its rows are paragraphs rather than nested containers. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.bugfix.2 b/frontend/packages/volto-light-theme/news/22.bugfix.2 new file mode 100644 index 0000000..4d5f533 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.bugfix.2 @@ -0,0 +1 @@ +Fixed the `documentByline` block presenting itself as a weather block: its schema title read "Previsão do Tempo", contradicting its registration title, and its exported data interface declared `location` and `measure` instead of the three fields the schema actually has. The block is now titled "Byline" throughout, and its boolean fields are no longer marked required — one of them defaults to `false`, which made the block unsaveable in a strict form. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.bugfix.3 b/frontend/packages/volto-light-theme/news/22.bugfix.3 new file mode 100644 index 0000000..12ad204 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.bugfix.3 @@ -0,0 +1 @@ +Fixed the Hero block's `fullWidth` option being unreachable. Both views branch on it to render full-bleed when there is no image, but it appeared in no fieldset, so no editor could set it. It is now part of the Layout fieldset. Also corrected a `--theme-low-contrast-foreground` reference in `_heroBlock.scss` that was missing its `-color` suffix and silently dropped the colour. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.bugfix.4 b/frontend/packages/volto-light-theme/news/22.bugfix.4 new file mode 100644 index 0000000..fae5c68 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.bugfix.4 @@ -0,0 +1 @@ +Fixed the carousel and highlight listing variations formatting dates as `pt-BR` regardless of the active language. Both now format through the locale from `react-intl`. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.bugfix.5 b/frontend/packages/volto-light-theme/news/22.bugfix.5 new file mode 100644 index 0000000..2e8374c --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.bugfix.5 @@ -0,0 +1 @@ +Fixed the header bar and accessibility control stories rendering out of context. The accessibility controls are drawn as light pills for the dark header bar, so on the story's hardcoded pale background they read as unstyled browser buttons and wrapped onto two lines; they now render inside the same `header-wrapper` → `header-bar-wrapper` → `header-bar` → `header-bar__inner` chain the header builds. The hardcoded greys in the header bar, accessibility and dropdown stories were replaced with theme tokens so they follow the colour mode, and two Portuguese labels left in the header bar mocks were translated. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.bugfix.6 b/frontend/packages/volto-light-theme/news/22.bugfix.6 new file mode 100644 index 0000000..25d8314 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.bugfix.6 @@ -0,0 +1 @@ +Fixed the navigation story decorator applying only part of the selector the stylesheet needs. `_navigation.scss` nests everything under `#navigation.navigation` and `&.scNavigation`, and the decorator supplied the id and the first class but not the second, so none of the rules matched: the fat menu rendered its content but `.submenu-items` never became a two-column grid and its links kept the browser's default colour. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.bugfix.7 b/frontend/packages/volto-light-theme/news/22.bugfix.7 new file mode 100644 index 0000000..3ea4c8c --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.bugfix.7 @@ -0,0 +1 @@ +Fixed five blocks erroring in the blocks gallery. `accordion` needs `data.data` with a nested `blocks_layout`, `slateTable` needs `data.table.rows`, and `highlight` reads `state.content.subrequests` keyed by block id — all three threw on undefined. `search` and `eventCalendar` dispatch thunks on mount, which the plain mock store rejects, so the gallery now uses Volto's real-store wrapper. All 42 registered blocks render without error. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.bugfix.8 b/frontend/packages/volto-light-theme/news/22.bugfix.8 new file mode 100644 index 0000000..d2e15a4 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.bugfix.8 @@ -0,0 +1 @@ +Fixed the fat menu stories rendering out of position and unstyled. The panel is absolutely positioned against the menu item that triggers it, so it needs the `.desktop-menu` list and an item with width; the open state is signalled by an `active` class, which two decorators had spelled differently. Every story is now also constrained to the default container width and centred, with an opt-out for the views and header that genuinely span the page. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.bugfix.9 b/frontend/packages/volto-light-theme/news/22.bugfix.9 new file mode 100644 index 0000000..3b23ea6 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.bugfix.9 @@ -0,0 +1 @@ +Aligned `ThemeSettings` type and `fixtures.ts` mock data to match the actual backend schema (which provides `_light` and `_dark` pairs directly). Removed redundant CSS token generation in `themeStyles.ts` and fixed the `COLOR_FIELD` validation regex to correctly process the `_light` and `_dark` color fields. @humanaice diff --git a/frontend/packages/volto-light-theme/news/22.feature b/frontend/packages/volto-light-theme/news/22.feature new file mode 100644 index 0000000..9779e09 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.feature @@ -0,0 +1 @@ +Made every user-facing string in the add-on translatable. All labels, block and variation titles, schema field titles and descriptions, editor placeholders and carousel `aria-label`s now go through `react-intl` with stable message ids and English default messages, replacing the hardcoded Portuguese that had no catalogue behind it. Block and listing-variation titles are registered for extraction in `src/index.ts`, since Volto translates those through `formatMessageWithFallback` using the string itself as the id. The extraction yields 147 messages and the `pt_BR` catalogue is complete; `es` and `de` remain empty. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.feature.2 b/frontend/packages/volto-light-theme/news/22.feature.2 new file mode 100644 index 0000000..4ff2789 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.feature.2 @@ -0,0 +1 @@ +Added a Storybook theme decorator with three toolbar controls: colour mode (light, dark, high contrast), block theme (Primary, Brand) and site theme (an `ISCVLTThemeDefinition` record). Stories previously rendered block views bare, so `--theme-*` was undefined in every one of them and Storybook never exercised the block theme system at all — HeroBlock alone carries 30 references to it, none of which resolved. The decorator reuses `createThemeDefinition`, so a theme added to the block config needs no change there. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.feature.3 b/frontend/packages/volto-light-theme/news/22.feature.3 new file mode 100644 index 0000000..d0da4a4 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.feature.3 @@ -0,0 +1 @@ +Added stories for the four listing variations — Grid, Highlight, Carousel and Media Carousel — with a shared fixture shaped like a `@querystring-search` response. Each renders inside the `block listing ` wrapper the listing block produces on a page, because `_listing.scss` is scoped to `body .block.listing`: without it none of the variation styling applies. This is what makes the restored focus outlines observable, and it closes the largest remaining gap in Storybook coverage. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.feature.4 b/frontend/packages/volto-light-theme/news/22.feature.4 new file mode 100644 index 0000000..d305006 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.feature.4 @@ -0,0 +1 @@ +Added page-level view stories rendering a Document and a News Item through Volto's `DefaultView`, with every block the add-on ships composed in one `blocks_layout`. Block stories show each block alone; these show the spacing between them, the container widths, and the `#page-document` container that several listing rules are scoped to. The fixture seeds the querystring results the listing block would otherwise fetch, so it renders real cards without a backend. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.feature.5 b/frontend/packages/volto-light-theme/news/22.feature.5 new file mode 100644 index 0000000..54c09bd --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.feature.5 @@ -0,0 +1 @@ +Added a blocks inventory and a blocks gallery to Storybook. The inventory reads `config.blocks.blocksConfig` at runtime, so it lists every block an editor actually has — 42 of them, of which this add-on ships four and extends two, the rest coming from Volto core, `@plone/volto-slate` and `@kitconcept/volto-light-theme`. The gallery renders each one through `RenderBlocks` with sample data, isolated behind a per-block error boundary so a block that throws is reported rather than blanking the page. Both update themselves when a dependency is bumped. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.feature.6 b/frontend/packages/volto-light-theme/news/22.feature.6 new file mode 100644 index 0000000..ec16557 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.feature.6 @@ -0,0 +1 @@ +Extended the Storybook coverage of blocks that come from dependencies. The social embed blocks now carry the sample payloads from their own stories in `@kitconcept/volto-social-blocks` rather than invented ones; `gridBlock` gets stories at one, two, three and four columns of image blocks with titles and captions; and the page-level composition gained a grid and an embed alongside the existing blocks. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.feature.7 b/frontend/packages/volto-light-theme/news/22.feature.7 new file mode 100644 index 0000000..00f8d27 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.feature.7 @@ -0,0 +1 @@ +Added an Event view story. Upstream volto-light-theme gives the Event type its own initial blocks — `title`, a fixed `eventMetadata` and `slate` — and the `eventMetadata` block reads the schedule, location and contact fields off the content item, which is what gives the type its distinct layout. The fixture carries those fields so the block renders as it would on a site. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.feature.8 b/frontend/packages/volto-light-theme/news/22.feature.8 new file mode 100644 index 0000000..26d404c --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.feature.8 @@ -0,0 +1 @@ +Added separator block stories covering its three controls — block width, the short-line toggle and alignment. Alignment only applies when `shortLine` is on, which its schema enforces by disabling the control otherwise, so the stories pair them rather than offering alignment on a full-width rule. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.feature.9 b/frontend/packages/volto-light-theme/news/22.feature.9 new file mode 100644 index 0000000..e6ed241 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.feature.9 @@ -0,0 +1 @@ +Added stories for the navigation components — the navigation itself, menu items, the fat menu, and its section header and item grid — covering the active states, sections with and without children, and missing descriptions. The fixtures and a scope decorator had been written for this in an earlier change but nothing consumed them. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.internal b/frontend/packages/volto-light-theme/news/22.internal new file mode 100644 index 0000000..e65c4fc --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.internal @@ -0,0 +1 @@ +Added a test guarding the contract between `createThemeDefinition` and `_root.scss`. Nothing else catches a theme property that the factory maps but the stylesheet never defines: a `var()` with no definition and no fallback is invalid at computed-value time, so the whole declaration is silently dropped — which is how six focus outlines in `_listing.scss` stopped rendering. The test reads the `:root` block specifically, because a token declared only under `[data-theme='high-contrast']` is still undefined in light and dark. Verified by reintroducing both original defects and confirming each one fails it. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.internal.2 b/frontend/packages/volto-light-theme/news/22.internal.2 new file mode 100644 index 0000000..527cfe4 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.internal.2 @@ -0,0 +1 @@ +Storybook now serves the add-on's `public` folder at its root, so a story can reference a bundled asset as `/images/example.jpg` instead of depending on a remote image host at view time. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.internal.3 b/frontend/packages/volto-light-theme/news/22.internal.3 new file mode 100644 index 0000000..2c87c71 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.internal.3 @@ -0,0 +1 @@ +Split the stories by ownership: a story for a component this package ships stays beside the component, and a story for a component from a dependency moved to `src/storybook/` next to the decorator and the shared fixtures. The blocks inventory and gallery, Volto's `DefaultView`, core's `gridBlock` and the separator block were filed under `components/` as though the add-on owned them. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.internal.4 b/frontend/packages/volto-light-theme/news/22.internal.4 new file mode 100644 index 0000000..ad11718 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.internal.4 @@ -0,0 +1 @@ +Prepared the add-on for export to Claude Design. `createThemeDefinition` moved to its own `config/blockThemes` module — `config/blocks` re-exports it, so every existing import is unchanged — because importing the factory from there registers every block and drags in the slate editor. Four modules under `src/storybook/` supply what a standalone bundle lacks: a curated component surface, Volto's razzle-time globals, a minimally seeded config registry, and the provider chain the storybook decorators would otherwise give a story. The icons in `ThemeToggle` and `HoverReaderControls` are now imported through the package name rather than a relative path, the same form `.storybook/preview.jsx` already used, so a bundler other than webpack can resolve them. @ericof diff --git a/frontend/packages/volto-light-theme/news/22.internal.5 b/frontend/packages/volto-light-theme/news/22.internal.5 new file mode 100644 index 0000000..faad1fc --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.internal.5 @@ -0,0 +1 @@ +Stopped publishing what projects do not consume. `.npmignore` now excludes stories, tests, their fixture assets, and the build products the design-system export emits — npm ignores `.gitignore` entirely once an `.npmignore` exists, so gitignored artifacts were being packed. The tarball drops from 404 files (1242 KB) to 192 (509 KB). @ericof diff --git a/frontend/packages/volto-light-theme/news/22.internal.6 b/frontend/packages/volto-light-theme/news/22.internal.6 new file mode 100644 index 0000000..cf7f8c4 --- /dev/null +++ b/frontend/packages/volto-light-theme/news/22.internal.6 @@ -0,0 +1 @@ +Replaced the story imagery with fourteen bundled photographs of known provenance. The fixtures pointed at `picsum.photos` — arbitrary third-party images, no licence trail, and a network request at view time. They now `import` files from `src/storybook/images/`, which webpack inlines for Storybook and esbuild inlines for the Claude Design export, so stories render offline and reproducibly. A new `imageScales()` helper shapes them the way Plone's REST API shapes an image field, and `optimize.sh` keeps them under a weight budget that matters because the export inlines them as data URIs. Also scoped the package's `README.md` ignore rule to the package root: a bare rule was hiding `src/storybook/README.md`, which `DESIGN_SYSTEM.md` links to, from git entirely. @ericof diff --git a/frontend/packages/volto-light-theme/public/images/README.md b/frontend/packages/volto-light-theme/public/images/README.md new file mode 100644 index 0000000..3bc8ed5 --- /dev/null +++ b/frontend/packages/volto-light-theme/public/images/README.md @@ -0,0 +1,11 @@ +# Public images + +Served at the Storybook root by `staticDirs` in `frontend/.storybook/main.js`, and +shipped inside the npm package, so anything here reaches every project that installs +the add-on. Keep it to assets a *running site* needs. + +**Story photographs do not belong here.** They live in `src/storybook/images/`, where +the fixtures `import` them: an import is inlined by webpack for Storybook and by +esbuild for the Claude Design export, so the previews work offline, and `.npmignore` +keeps the weight out of the published package. A file here would instead be fetched +by URL at view time — which resolves to nothing in a Claude Design preview. diff --git a/frontend/packages/volto-light-theme/public/images/placeholder.svg b/frontend/packages/volto-light-theme/public/images/placeholder.svg new file mode 100644 index 0000000..3218dbb --- /dev/null +++ b/frontend/packages/volto-light-theme/public/images/placeholder.svg @@ -0,0 +1,7 @@ + + + + + placeholder + diff --git a/frontend/packages/volto-light-theme/src/components/AccessibilityControls/AccessibilityControls.stories.tsx b/frontend/packages/volto-light-theme/src/components/AccessibilityControls/AccessibilityControls.stories.tsx index 1d9fdd0..530a14a 100644 --- a/frontend/packages/volto-light-theme/src/components/AccessibilityControls/AccessibilityControls.stories.tsx +++ b/frontend/packages/volto-light-theme/src/components/AccessibilityControls/AccessibilityControls.stories.tsx @@ -15,9 +15,27 @@ const withStoredScale: Decorator = (Story, context) => { } return ( -
- -

+

+ {/* + The controls are styled for the dark header bar — `_accessibilityControls.scss` + gives the buttons a light ground and expects `--secondary-color` behind + them. Rendered on a pale page they read as unstyled browser buttons, and + without the bar's flex row they wrap. This is the chain `HeaderBar` builds. + */} +
+
+
+
+
+ +
+
+
+
+
+

Hover over this text to test the hover reader! It will read the text out loud using window.speechSynthesis. You can also test the font size controls. diff --git a/frontend/packages/volto-light-theme/src/components/AccessibilityControls/FontSizeControls.tsx b/frontend/packages/volto-light-theme/src/components/AccessibilityControls/FontSizeControls.tsx index 2b43745..5aeddbb 100644 --- a/frontend/packages/volto-light-theme/src/components/AccessibilityControls/FontSizeControls.tsx +++ b/frontend/packages/volto-light-theme/src/components/AccessibilityControls/FontSizeControls.tsx @@ -15,11 +15,11 @@ const FONT_SCALE_STEP = 0.1; const messages = defineMessages({ increaseFontSize: { id: 'accessibilityIncreaseFontSize', - defaultMessage: 'Aumentar fonte', + defaultMessage: 'Increase font size', }, decreaseFontSize: { id: 'accessibilityDecreaseFontSize', - defaultMessage: 'Diminuir fonte', + defaultMessage: 'Decrease font size', }, }); diff --git a/frontend/packages/volto-light-theme/src/components/AccessibilityControls/HoverReaderControls.tsx b/frontend/packages/volto-light-theme/src/components/AccessibilityControls/HoverReaderControls.tsx index d32f86b..c77e648 100644 --- a/frontend/packages/volto-light-theme/src/components/AccessibilityControls/HoverReaderControls.tsx +++ b/frontend/packages/volto-light-theme/src/components/AccessibilityControls/HoverReaderControls.tsx @@ -2,8 +2,8 @@ import React, { useCallback, useEffect, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import Icon from '@plone/volto/components/theme/Icon/Icon'; -import speedSVG from '../../icons/speed.svg'; -import earSVG from '../../icons/ear.svg'; +import speedSVG from '@simplesconsultoria/volto-light-theme/icons/speed.svg'; +import earSVG from '@simplesconsultoria/volto-light-theme/icons/ear.svg'; import { isSpeechSupported, ensureVoicesLoaded, @@ -29,11 +29,11 @@ const TEXT_TARGET_SELECTOR = const messages = defineMessages({ hoverReaderLabel: { id: 'accessibilityHoverReaderLabel', - defaultMessage: 'Leitura ao passar o mouse', + defaultMessage: 'Read on hover', }, speedLabel: { id: 'accessibilitySpeedLabel', - defaultMessage: 'Velocidade do leitor', + defaultMessage: 'Reader speed', }, }); diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/DocumentByLine/index.ts b/frontend/packages/volto-light-theme/src/components/Blocks/DocumentByLine/index.ts index 93ad2df..a611764 100644 --- a/frontend/packages/volto-light-theme/src/components/Blocks/DocumentByLine/index.ts +++ b/frontend/packages/volto-light-theme/src/components/Blocks/DocumentByLine/index.ts @@ -5,13 +5,14 @@ import DocumentByLineSVG from '@plone/volto/icons/pencil.svg'; import type { BlockConfigBase } from '@plone/types'; export interface DocumentByLineBlockData { - location?: string; - measure?: string; + showPublished?: boolean; + showModified?: boolean; + showAuthor?: boolean; } const DocumentByLineBlockInfo: BlockConfigBase = { id: 'documentByline', - title: 'Assinatura', + title: 'Byline', icon: DocumentByLineSVG, group: 'common', view: DocumentByLineBlockView, diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/DocumentByLine/schema.ts b/frontend/packages/volto-light-theme/src/components/Blocks/DocumentByLine/schema.ts index de64b3b..b77cb12 100644 --- a/frontend/packages/volto-light-theme/src/components/Blocks/DocumentByLine/schema.ts +++ b/frontend/packages/volto-light-theme/src/components/Blocks/DocumentByLine/schema.ts @@ -2,21 +2,21 @@ import { defineMessages } from 'react-intl'; import type { BlockSchemaProps } from '@plone/types'; const messages = defineMessages({ - DocumentByLineBlock: { - id: 'Previsão do Tempo', - defaultMessage: 'Previsão do Tempo', + documentByline: { + id: 'Byline', + defaultMessage: 'Byline', }, showPublished: { - id: 'Exibir data de publicação?', - defaultMessage: 'Exibir data de publicação?', + id: 'Show publication date', + defaultMessage: 'Show publication date', }, showModified: { - id: 'Exibir última modificação?', - defaultMessage: 'Exibir última modificação?', + id: 'Show last modification date', + defaultMessage: 'Show last modification date', }, showAuthor: { - id: 'Exibir autor?', - defaultMessage: 'Exibir autor?', + id: 'Show author', + defaultMessage: 'Show author', }, }); @@ -24,7 +24,7 @@ export const DocumentByLineSchema = (props: BlockSchemaProps): any => { const { intl } = props; return { - title: intl.formatMessage(messages.DocumentByLineBlock), + title: intl.formatMessage(messages.documentByline), fieldsets: [ { id: 'default', @@ -50,6 +50,6 @@ export const DocumentByLineSchema = (props: BlockSchemaProps): any => { }, }, - required: ['showPublished', 'showModified', 'showAuthor'], + required: [], }; }; diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/HeroBlock/HeroBlock.stories.tsx b/frontend/packages/volto-light-theme/src/components/Blocks/HeroBlock/HeroBlock.stories.tsx index 1380e57..9e4fff7 100644 --- a/frontend/packages/volto-light-theme/src/components/Blocks/HeroBlock/HeroBlock.stories.tsx +++ b/frontend/packages/volto-light-theme/src/components/Blocks/HeroBlock/HeroBlock.stories.tsx @@ -3,6 +3,7 @@ import type { Decorator, Meta, StoryObj } from '@storybook/react'; import Wrapper from '@plone/volto/storybook'; import View from './View'; +import heroImage from '../../../storybook/images/hero.jpg'; const withWrapper: Decorator = (Story, context) => { return ( @@ -33,18 +34,18 @@ type Story = StoryObj; const sampleData = { '@type': 'heroBlock', overwrite: true, - title: 'Isso é um Título de Destaque', + title: 'This is a Highlighted Title', description: - 'Uma breve descrição sobre o assunto principal para engajar o leitor.', - headerText: 'Notícia', - footerText: 'Fonte oficial', + 'A short description of the main topic, written to engage the reader.', + headerText: 'News', + footerText: 'Official source', button: true, - buttonText: 'Saiba mais', - url: 'https://picsum.photos/seed/hero/800/600', + buttonText: 'Read more', + url: heroImage, date: '2026-06-17T16:33:00-03:00', showDate: true, - tags: ['Brasil', 'Diracom'], - fileType: 'Artigo', + tags: ['Policy', 'Community'], + fileType: 'Article', }; export const FlexLeft: Story = { @@ -66,7 +67,7 @@ export const FlexRightFull: Story = { variation: 'flex', textSide: 'right', fullWidth: true, - url: null, // Sem imagem para testar o fullWidth + url: null, // no image, to exercise fullWidth }, }, }; diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/HeroBlock/index.ts b/frontend/packages/volto-light-theme/src/components/Blocks/HeroBlock/index.ts index 7992932..b56ce44 100644 --- a/frontend/packages/volto-light-theme/src/components/Blocks/HeroBlock/index.ts +++ b/frontend/packages/volto-light-theme/src/components/Blocks/HeroBlock/index.ts @@ -23,12 +23,12 @@ const HeroBlockInfo: BlockConfigBase = { variations: [ { id: 'flex', - title: 'Flex / Decorativa', + title: 'Flex / Decorative', isDefault: true, }, { id: 'card', - title: 'Card Colado', + title: 'Attached Card', }, ], }; diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/HeroBlock/schema.ts b/frontend/packages/volto-light-theme/src/components/Blocks/HeroBlock/schema.ts index 061b3dd..53974e1 100644 --- a/frontend/packages/volto-light-theme/src/components/Blocks/HeroBlock/schema.ts +++ b/frontend/packages/volto-light-theme/src/components/Blocks/HeroBlock/schema.ts @@ -4,97 +4,175 @@ import { defaultStylingSchema } from '../schema'; const messages = defineMessages({ heroBlock: { - id: 'heroBlock', + id: 'Hero Block', defaultMessage: 'Hero Block', }, + content: { + id: 'Content', + defaultMessage: 'Content', + }, + overwrite: { + id: 'Overwrite content', + defaultMessage: 'Overwrite content', + }, + overwriteDescription: { + id: 'Overwrite content description', + defaultMessage: + 'Check to customise the title, description and image instead of pulling them automatically from the highlighted item', + }, headerText: { - id: 'heroHeaderText', - defaultMessage: 'Texto do cabeçalho', + id: 'Header text', + defaultMessage: 'Header text', + }, + headerTextDescription: { + id: 'Header text description', + defaultMessage: 'Small text displayed above the main title.', }, title: { id: 'Title', - defaultMessage: 'Título', + defaultMessage: 'Title', + }, + titleDescription: { + id: 'Hero title description', + defaultMessage: 'Main highlighted title.', }, description: { id: 'Description', - defaultMessage: 'Descrição', + defaultMessage: 'Description', + }, + descriptionDescription: { + id: 'Hero description description', + defaultMessage: 'Explanatory text or summary displayed below the title.', }, footerText: { - id: 'heroFooterText', - defaultMessage: 'Texto do rodapé', + id: 'Footer text', + defaultMessage: 'Footer text', + }, + footerTextDescription: { + id: 'Footer text description', + defaultMessage: + 'Additional information displayed at the foot of the block.', }, showDate: { - id: 'heroShowDate', - defaultMessage: 'Mostrar data', + id: 'Show date', + defaultMessage: 'Show date', }, tags: { - id: 'heroTags', + id: 'Tags', defaultMessage: 'Tags', }, image: { id: 'Image', - defaultMessage: 'Imagem', + defaultMessage: 'Image', + }, + hideImage: { + id: 'Hide image', + defaultMessage: 'Hide image', + }, + imageFit: { + id: 'Image fit', + defaultMessage: 'Image fit', + }, + imageFitCover: { + id: 'Cover', + defaultMessage: 'Cover', }, - imageFieldset: { - id: 'heroImageFieldset', - defaultMessage: 'Imagem', + imageFitContain: { + id: 'Contain', + defaultMessage: 'Contain', + }, + imageFitDescription: { + id: 'Image fit description', + defaultMessage: + '"Cover" fills the whole available space, cropping the edges of the image if needed. "Contain" fits the entire image inside the space, which may leave empty bands at the sides.', }, cta: { - id: 'heroCTA', + id: 'Call to Action', defaultMessage: 'Call to Action', }, button: { - id: 'Button', - defaultMessage: 'Mostrar botão', + id: 'Show button', + defaultMessage: 'Show button', }, buttonLink: { - id: 'buttonLink', - defaultMessage: 'Link do botão', + id: 'Button link', + defaultMessage: 'Button link', }, buttonText: { - id: 'buttonText', - defaultMessage: 'Texto do botão', + id: 'Button text', + defaultMessage: 'Button text', + }, + buttonTextDescription: { + id: 'Button text description', + defaultMessage: 'The text shown inside the button (e.g. "Read more").', }, layout: { - id: 'heroLayout', + id: 'Layout', defaultMessage: 'Layout', }, fullWidth: { - id: 'heroFullWidth', - defaultMessage: 'Largura total', + id: 'Full width', + defaultMessage: 'Full width', }, - textSide: { - id: 'heroTextSide', - defaultMessage: 'Lado do texto', + fullWidthDescription: { + id: 'Full width description', + defaultMessage: + 'Let the text span the whole block when there is no image to show.', }, - textSideLeft: { - id: 'heroTextSideLeft', - defaultMessage: 'Esquerda', + blockWidth: { + id: 'Block Width', + defaultMessage: 'Block Width', }, - textSideRight: { - id: 'heroTextSideRight', - defaultMessage: 'Direita', + imageSize: { + id: 'Image size / empty space', + defaultMessage: 'Image size / empty space', }, - highlightItem: { - id: 'heroHighlightItem', - defaultMessage: 'Item de Destaque', + imageSizeDescription: { + id: 'Image size description', + defaultMessage: + 'Controls the width reserved for the image (even when there is none), which in turn sets the space for the text.', + }, + imageSizeNone: { + id: 'Image size none', + defaultMessage: '0% (text full width)', + }, + imageSizeHalf: { + id: 'Image size half', + defaultMessage: '50% (half)', + }, + titleTag: { + id: 'Title element', + defaultMessage: 'Title element', }, - href: { - id: 'heroHref', - defaultMessage: 'Item de destaque', + titleTagParagraph: { + id: 'Paragraph (P)', + defaultMessage: 'Paragraph (P)', + }, + titleTagDescription: { + id: 'Title element description', + defaultMessage: + 'H1 is the main page title, H2 the secondary title and H3 the tertiary one. P renders the title as a paragraph.', + }, + textSide: { + id: 'Text side', + defaultMessage: 'Text side', + }, + highlightItem: { + id: 'Highlighted item', + defaultMessage: 'Highlighted item', }, hrefDescription: { - id: 'heroHrefDescription', + id: 'Highlighted item description', defaultMessage: - 'Selecione um conteúdo para puxar dados automaticamente (título, descrição, imagem)', + 'Select a content item to pull its data automatically (title, description, image)', }, fileType: { - id: 'heroFileType', - defaultMessage: 'Tipo do arquivo', + id: 'File type', + defaultMessage: 'File type', }, - styling: { - id: 'Styling', - defaultMessage: 'Estilo', + fileTypeDescription: { + id: 'File type description', + defaultMessage: 'E.g. PDF, Report, Article', }, }); @@ -105,7 +183,13 @@ interface HeroSchemaProps { [key: string]: any; } -const layoutFields = ['blockWidth', 'textSide', 'imageSize', 'titleTag']; +const layoutFields = [ + 'blockWidth', + 'textSide', + 'imageSize', + 'fullWidth', + 'titleTag', +]; export function HeroBlockSchema(props: HeroSchemaProps): any { const { intl } = props; @@ -137,12 +221,12 @@ export function HeroBlockSchema(props: HeroSchemaProps): any { fieldsets: [ { id: 'default', - title: 'Conteúdo', + title: intl.formatMessage(messages.content), fields: defaultFields, }, { id: 'image', - title: intl.formatMessage(messages.imageFieldset), + title: intl.formatMessage(messages.image), fields: formData.overwrite ? ['preview_image', 'hideImage', 'imageFit'] : ['hideImage', 'imageFit'], @@ -160,15 +244,14 @@ export function HeroBlockSchema(props: HeroSchemaProps): any { ], properties: { overwrite: { - title: 'Sobrescrever conteúdo', - description: - 'Marque para customizar o título, descrição e imagem ao invés de puxar automaticamente do item de destaque', + title: intl.formatMessage(messages.overwrite), + description: intl.formatMessage(messages.overwriteDescription), type: 'boolean', default: false, }, headerText: { title: intl.formatMessage(messages.headerText), - description: 'Texto pequeno exibido acima do título principal.', + description: intl.formatMessage(messages.headerTextDescription), type: 'string', }, showDate: { @@ -178,18 +261,18 @@ export function HeroBlockSchema(props: HeroSchemaProps): any { }, title: { title: intl.formatMessage(messages.title), - description: 'Título principal em destaque.', + description: intl.formatMessage(messages.titleDescription), type: 'string', }, description: { title: intl.formatMessage(messages.description), - description: 'Texto explicativo ou resumo exibido abaixo do título.', + description: intl.formatMessage(messages.descriptionDescription), type: 'string', widget: 'textarea', }, footerText: { title: intl.formatMessage(messages.footerText), - description: 'Informação adicional exibida no rodapé do bloco.', + description: intl.formatMessage(messages.footerTextDescription), type: 'string', }, tags: { @@ -202,7 +285,7 @@ export function HeroBlockSchema(props: HeroSchemaProps): any { fileType: { title: intl.formatMessage(messages.fileType), type: 'string', - description: 'Ex: PDF, Relatório, Artigo', + description: intl.formatMessage(messages.fileTypeDescription), }, preview_image: { title: intl.formatMessage(messages.image), @@ -212,20 +295,19 @@ export function HeroBlockSchema(props: HeroSchemaProps): any { selectedItemAttrs: ['image_field', 'image_scales'], }, hideImage: { - title: 'Esconder Imagem', + title: intl.formatMessage(messages.hideImage), type: 'boolean', default: false, }, imageFit: { - title: 'Ajuste da Imagem', + title: intl.formatMessage(messages.imageFit), type: 'string', choices: [ - ['cover', 'Preencher (Cover)'], - ['contain', 'Conter (Contain)'], + ['cover', intl.formatMessage(messages.imageFitCover)], + ['contain', intl.formatMessage(messages.imageFitContain)], ], default: 'cover', - description: - 'O "Cover" preenche todo o espaço disponível, cortando as bordas da imagem se necessário. O "Contain" ajusta a imagem inteira dentro do espaço, podendo deixar faixas laterais vazias.', + description: intl.formatMessage(messages.imageFitDescription), }, button: { title: intl.formatMessage(messages.button), @@ -239,47 +321,45 @@ export function HeroBlockSchema(props: HeroSchemaProps): any { }, buttonText: { title: intl.formatMessage(messages.buttonText), - description: - 'O texto que aparecerá dentro do botão (ex: "Saiba mais").', + description: intl.formatMessage(messages.buttonTextDescription), type: 'string', }, fullWidth: { title: intl.formatMessage(messages.fullWidth), + description: intl.formatMessage(messages.fullWidthDescription), type: 'boolean', default: false, }, blockWidth: { - title: 'Largura do Bloco', + title: intl.formatMessage(messages.blockWidth), widget: 'blockWidth', default: 'layout', }, imageSize: { - title: 'Tamanho da Imagem / Espaço Vazio', - description: - 'Controla a largura reservada para a imagem (mesmo se não houver uma), definindo o espaço do texto.', + title: intl.formatMessage(messages.imageSize), + description: intl.formatMessage(messages.imageSizeDescription), type: 'string', choices: [ - ['0%', '0% (Texto 100%)'], + ['0%', intl.formatMessage(messages.imageSizeNone)], ['30%', '30%'], ['40%', '40%'], - ['50%', '50% (Metade)'], + ['50%', intl.formatMessage(messages.imageSizeHalf)], ['60%', '60%'], ['70%', '70%'], ], default: '50%', }, titleTag: { - title: 'Elemento do Título', + title: intl.formatMessage(messages.titleTag), type: 'string', choices: [ ['h1', 'H1'], ['h2', 'H2'], ['h3', 'H3'], - ['p', 'Parágrafo (P)'], + ['p', intl.formatMessage(messages.titleTagParagraph)], ], default: 'h2', - description: - 'O H1 é o título principal da página. O H2 é o título secundário. O H3 é o título terciário. O P é o parágrafo.', + description: intl.formatMessage(messages.titleTagDescription), }, textSide: { title: intl.formatMessage(messages.textSide), @@ -288,7 +368,7 @@ export function HeroBlockSchema(props: HeroSchemaProps): any { default: 'left', }, href: { - title: intl.formatMessage(messages.href), + title: intl.formatMessage(messages.highlightItem), description: intl.formatMessage(messages.hrefDescription), widget: 'object_browser', mode: 'link', diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/HeroBlock/useHeroBlockContent.ts b/frontend/packages/volto-light-theme/src/components/Blocks/HeroBlock/useHeroBlockContent.ts index 69e5be5..92d2bb9 100644 --- a/frontend/packages/volto-light-theme/src/components/Blocks/HeroBlock/useHeroBlockContent.ts +++ b/frontend/packages/volto-light-theme/src/components/Blocks/HeroBlock/useHeroBlockContent.ts @@ -1,7 +1,20 @@ import { useMemo } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; import type { HeroBlockData } from './types'; import { imageInfoFromHeroBlock } from './imageInformation'; +const messages = defineMessages({ + selectHighlightItem: { + id: 'Select a highlighted item', + defaultMessage: 'Select a highlighted item', + }, + emptyHeroBlock: { + id: 'Empty hero block placeholder', + defaultMessage: + 'This is a Hero Block. Add an item in the sidebar to fill it automatically.', + }, +}); + interface UseHeroBlockContentProps { data: HeroBlockData; isEditMode?: boolean; @@ -13,6 +26,8 @@ export function useHeroBlockContent({ isEditMode, defaultTitleTag = 'h2', }: UseHeroBlockContentProps) { + const intl = useIntl(); + return useMemo(() => { const hrefItem = data.href?.[0]; @@ -27,13 +42,12 @@ export function useHeroBlockContent({ : hrefItem?.Description || hrefItem?.description || data.description; const displayTitle = - title || (isEditMode ? 'Selecione um Item de Destaque' : ''); + title || + (isEditMode ? intl.formatMessage(messages.selectHighlightItem) : ''); const displayDescription = description || - (isEditMode && !title - ? 'Este é um Hero Block. Por favor, adicione um item no menu lateral para preenchê-lo automaticamente.' - : ''); + (isEditMode && !title ? intl.formatMessage(messages.emptyHeroBlock) : ''); const date = hrefItem?.EffectiveDate || @@ -72,5 +86,5 @@ export function useHeroBlockContent({ imageInfo, hasImage, }; - }, [data, isEditMode, defaultTitleTag]); + }, [data, isEditMode, defaultTitleTag, intl]); } diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/Listing/CarouselTemplate.stories.tsx b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/CarouselTemplate.stories.tsx new file mode 100644 index 0000000..1c02766 --- /dev/null +++ b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/CarouselTemplate.stories.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import type { Decorator, Meta, StoryObj } from '@storybook/react'; +import Wrapper from '@plone/volto/storybook'; + +import CarouselTemplate from './CarouselTemplate'; +import { listingItems, singleItem } from './fixtures'; + +/** + * `_listing.scss` is scoped to `body .block.listing`, and the variation rules to + * `&.carousel`, so the template has to sit inside the same wrapper the listing + * block renders on a page — otherwise none of its styling applies, including the + * focus outlines on cards. + */ +const withWrapper: Decorator = (Story) => ( + +

+
+ +
+
+ +); + +const meta = { + title: 'Public/Blocks/Listing/Carousel', + component: CarouselTemplate, + decorators: [withWrapper], + parameters: { layout: 'fullscreen' }, + tags: ['autodocs'], + argTypes: { + carouselObjectFit: { + control: { type: 'inline-radio' }, + options: ['contain', 'cover'], + }, + carouselMaxHeight: { + control: { type: 'range', min: 200, max: 1200, step: 20 }, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + items: listingItems, + carouselMaxHeight: 520, + carouselObjectFit: 'cover', + carouselObjectPosition: 'center center', + }, +}; + +/** `contain` leaves bands at the sides rather than cropping. */ +export const Contain: Story = { + args: { ...Default.args, carouselObjectFit: 'contain' }, +}; + +/** Auto-advance adds the play/pause control to the toolbar. */ +export const AutoPlay: Story = { + args: { + ...Default.args, + carouselAutoPlay: true, + carouselAutoPlayInterval: 6000, + }, +}; + +export const WithHeadlineLink: Story = { + args: { + ...Default.args, + linkTitle: 'See all news', + linkHref: [{ '@id': '/news' }], + }, +}; + +/** A single slide still renders the dot strip, with navigation a no-op. */ +export const SingleItem: Story = { + args: { ...Default.args, items: singleItem }, +}; diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/Listing/CarouselTemplate.tsx b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/CarouselTemplate.tsx index 5af58de..4c75333 100644 --- a/frontend/packages/volto-light-theme/src/components/Blocks/Listing/CarouselTemplate.tsx +++ b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/CarouselTemplate.tsx @@ -1,10 +1,13 @@ import React, { useEffect, useState } from 'react'; +import { useIntl } from 'react-intl'; import cx from 'classnames'; import ConditionalLink from '@plone/volto/components/manage/ConditionalLink/ConditionalLink'; import Icon from '@plone/volto/components/theme/Icon/Icon'; import config from '@plone/volto/registry'; import { flattenToAppURL, isInternalURL } from '@plone/volto/helpers/Url/Url'; +import { carouselMessages } from './messages'; + import leftSVG from '@plone/volto/icons/left-key.svg'; import rightSVG from '@plone/volto/icons/right-key.svg'; import playSVG from '@plone/volto/icons/play.svg'; @@ -39,10 +42,10 @@ const isValidDate = (dateString?: string): boolean => { } }; -const formatDate = (dateString?: string): string => { +const formatDate = (dateString: string | undefined, locale: string): string => { if (!isValidDate(dateString)) return ''; const date = new Date(dateString as string); - return date.toLocaleDateString('pt-BR', { + return date.toLocaleDateString(locale, { day: '2-digit', month: '2-digit', year: 'numeric', @@ -103,6 +106,7 @@ const CarouselTemplate: React.FC< carouselAutoPlay, carouselAutoPlayInterval, }) => { + const intl = useIntl(); const slides = items.filter(Boolean); const PreviewImageComponent = config.getComponent('PreviewImage').component; const [activeIndex, setActiveIndex] = useState(0); @@ -180,7 +184,7 @@ const CarouselTemplate: React.FC<
setIsPlaying(false)} >
@@ -295,7 +299,7 @@ const CarouselTemplate: React.FC<
{slides.map((item, index) => ( diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/Listing/GridTemplate.stories.tsx b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/GridTemplate.stories.tsx new file mode 100644 index 0000000..016ea41 --- /dev/null +++ b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/GridTemplate.stories.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import type { Decorator, Meta, StoryObj } from '@storybook/react'; +import Wrapper from '@plone/volto/storybook'; + +import GridTemplate from './GridTemplate'; +import { listingItems, singleItem } from './fixtures'; + +/** + * `_listing.scss` is scoped to `body .block.listing`, and the variation rules to + * `&.grid`, so the template has to sit inside the same wrapper the listing + * block renders on a page — otherwise none of its styling applies, including the + * focus outlines on cards. + */ +const withWrapper: Decorator = (Story) => ( + +
+
+ +
+
+
+); + +const meta = { + title: 'Public/Blocks/Listing/Grid', + component: GridTemplate, + decorators: [withWrapper], + parameters: { layout: 'fullscreen' }, + tags: ['autodocs'], + argTypes: { + gridColumns: { control: { type: 'inline-radio' }, options: [1, 2, 3, 4] }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const ThreeColumns: Story = { + args: { items: listingItems, gridColumns: 3 }, +}; + +export const TwoColumns: Story = { + args: { items: listingItems, gridColumns: 2 }, +}; + +export const FourColumns: Story = { + args: { items: listingItems, gridColumns: 4 }, +}; + +export const WithHeadlineLink: Story = { + args: { + items: listingItems.slice(0, 3), + gridColumns: 3, + linkTitle: 'See all publications', + linkHref: [{ '@id': '/publications' }], + }, +}; + +export const SingleItem: Story = { + args: { items: singleItem, gridColumns: 3 }, +}; diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/Listing/MediaCarouselTemplate.stories.tsx b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/MediaCarouselTemplate.stories.tsx new file mode 100644 index 0000000..24d032f --- /dev/null +++ b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/MediaCarouselTemplate.stories.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import type { Decorator, Meta, StoryObj } from '@storybook/react'; +import Wrapper from '@plone/volto/storybook'; + +import MediaCarouselTemplate from './MediaCarouselTemplate'; +import { mediaItems, listingItems } from './fixtures'; + +/** + * `_listing.scss` is scoped to `body .block.listing`, and the variation rules to + * `&.mediaCarousel`, so the template has to sit inside the same wrapper the listing + * block renders on a page — otherwise none of its styling applies, including the + * focus outlines on cards. + */ +const withWrapper: Decorator = (Story) => ( + +
+
+ +
+
+
+); + +const meta = { + title: 'Public/Blocks/Listing/Media Carousel', + component: MediaCarouselTemplate, + decorators: [withWrapper], + parameters: { layout: 'fullscreen' }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +/** Mixed media: the first two items carry a `videoUrl`, the third does not. */ +export const Default: Story = { + args: { + items: mediaItems, + carouselMaxHeight: 520, + carouselObjectFit: 'cover', + carouselObjectPosition: 'center center', + }, +}; + +/** No `videoUrl` anywhere, so every slide falls back to its preview image. */ +export const ImagesOnly: Story = { + args: { ...Default.args, items: listingItems.slice(0, 3) }, +}; + +export const AutoPlay: Story = { + args: { + ...Default.args, + carouselAutoPlay: true, + carouselAutoPlayInterval: 6000, + }, +}; diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/Listing/MediaCarouselTemplate.tsx b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/MediaCarouselTemplate.tsx index f0f1692..3df12d9 100644 --- a/frontend/packages/volto-light-theme/src/components/Blocks/Listing/MediaCarouselTemplate.tsx +++ b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/MediaCarouselTemplate.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import { useIntl } from 'react-intl'; import cx from 'classnames'; import Icon from '@plone/volto/components/theme/Icon/Icon'; import config from '@plone/volto/registry'; @@ -8,6 +9,8 @@ import rightSVG from '@plone/volto/icons/right-key.svg'; import playSVG from '@plone/volto/icons/play.svg'; import pauseSVG from '@plone/volto/icons/pause.svg'; +import { carouselMessages } from './messages'; + type ListingItem = Record; interface MediaCarouselTemplateProps { @@ -64,6 +67,7 @@ const MediaCarouselTemplate: React.FC< carouselAutoPlay, carouselAutoPlayInterval, }) => { + const intl = useIntl(); const slides = items.filter(Boolean); const PreviewImageComponent = config.getComponent('PreviewImage').component; const [activeIndex, setActiveIndex] = useState(0); @@ -129,7 +133,7 @@ const MediaCarouselTemplate: React.FC<
setIsPlaying(false)} >
@@ -208,7 +214,7 @@ const MediaCarouselTemplate: React.FC<
{slides.map((item, index) => ( diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/Listing/TeaserTemplate.stories.tsx b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/TeaserTemplate.stories.tsx new file mode 100644 index 0000000..185d907 --- /dev/null +++ b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/TeaserTemplate.stories.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import type { Decorator, Meta, StoryObj } from '@storybook/react'; +import Wrapper from '@plone/volto/storybook'; + +import TeaserTemplate from './TeaserTemplate'; +import { listingItems, singleItem } from './fixtures'; + +/** + * `_listing.scss` is scoped to `body .block.listing`, and the variation rules to + * `&.teaser`, so the template has to sit inside the same wrapper the listing + * block renders on a page — otherwise none of its styling applies, including the + * focus outlines on cards. + */ +const withWrapper: Decorator = (Story) => ( + +
+
+ +
+
+
+); + +const meta = { + title: 'Public/Blocks/Listing/Highlight', + component: TeaserTemplate, + decorators: [withWrapper], + parameters: { layout: 'fullscreen' }, + tags: ['autodocs'], + argTypes: { + align: { + control: { type: 'inline-radio' }, + options: ['left', 'center', 'right'], + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const AlignLeft: Story = { + args: { items: listingItems.slice(0, 3), align: 'left' }, +}; + +export const AlignRight: Story = { + args: { items: listingItems.slice(0, 3), align: 'right' }, +}; + +export const AlignCenter: Story = { + args: { items: listingItems.slice(0, 3), align: 'center' }, +}; + +export const SingleItem: Story = { + args: { items: singleItem, align: 'left' }, +}; diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/Listing/TeaserTemplate.tsx b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/TeaserTemplate.tsx index 02c0b44..e6b81ae 100644 --- a/frontend/packages/volto-light-theme/src/components/Blocks/Listing/TeaserTemplate.tsx +++ b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/TeaserTemplate.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useIntl } from 'react-intl'; import cx from 'classnames'; import config from '@plone/volto/registry'; import { isInternalURL } from '@plone/volto/helpers/Url/Url'; @@ -12,14 +13,14 @@ interface TeaserTemplateProps { data?: any; } -const formatDate = (dateString?: string): string => { +const formatDate = (dateString: string | undefined, locale: string): string => { if (!dateString) return ''; try { const date = new Date(dateString); if (isNaN(date.getTime())) return ''; return date.getTime() === 0 ? '' - : date.toLocaleDateString('pt-BR', { + : date.toLocaleDateString(locale, { day: '2-digit', month: '2-digit', year: 'numeric', @@ -53,6 +54,7 @@ const TeaserTemplate: React.FC> = ({ align: alignProp, ...rest }) => { + const intl = useIntl(); const data = rest.data || rest; const Image = config.getComponent('Image').component; const { openExternalLinkInNewTab } = config.settings; @@ -70,6 +72,7 @@ const TeaserTemplate: React.FC> = ({ {items.map((item, index) => { const date = formatDate( item?.Date || item?.effective || item?.CreationDate || item?.created, + intl.locale, ); const openLinkInNewTab = diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/Listing/fixtures.ts b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/fixtures.ts new file mode 100644 index 0000000..42d0438 --- /dev/null +++ b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/fixtures.ts @@ -0,0 +1,132 @@ +/** + * Listing items shared by the listing-variation stories. + * + * Shaped like what `@querystring-search` returns for a listing block: only the + * fields the templates actually read are present, and `@id` values are already + * flattened to app-relative URLs the way the reducer leaves them. + * + * Not a `*.stories.*` file, so Storybook does not try to render it. + */ + +import listing01 from '../../../storybook/images/listing-01.jpg'; +import listing02 from '../../../storybook/images/listing-02.jpg'; +import listing03 from '../../../storybook/images/listing-03.jpg'; +import listing04 from '../../../storybook/images/listing-04.jpg'; +import listing05 from '../../../storybook/images/listing-05.jpg'; +import listing06 from '../../../storybook/images/listing-06.jpg'; + +import { imageScales } from '../../../storybook/images/imageScales'; + +type ListingItem = Record; + +/** + * Photography bundled with the package, so cards render with no backend and no + * network behind Storybook. + * + * This still exercises the `base_path` branch of Volto's `Image` — the same one + * `preview_image_link` takes on a real site — because :func:`imageScales` splits + * the imported URL across `base_path` and `download` exactly as the API would. + */ +const image = (url: string) => ({ + image_field: 'preview_image', + image_scales: { preview_image: [imageScales(url)] }, +}); + +export const listingItems: ListingItem[] = [ + { + '@id': '/news/budget-hearing-draws-a-full-room', + '@type': 'News Item', + id: 'budget-hearing-draws-a-full-room', + title: 'Budget hearing draws a full room', + head_title: 'Public finance', + description: + 'Residents filled the chamber to question next year’s spending plan, with most of the debate turning on transport.', + effective: '2026-08-14T10:00:00+00:00', + ...image(listing01), + }, + { + '@id': '/documents/open-data-policy', + '@type': 'Document', + id: 'open-data-policy', + title: 'The open data policy, explained', + head_title: 'Transparency', + description: + 'What the new policy covers, which datasets are published first, and how to request one that is missing.', + effective: '2026-07-30T09:30:00+00:00', + ...image(listing02), + }, + { + '@id': '/events/accessibility-workshop', + '@type': 'Event', + id: 'accessibility-workshop', + title: 'Accessibility workshop for content editors', + head_title: 'Training', + description: + 'A hands-on session on alt text, heading structure and colour contrast, aimed at anyone who publishes to the site.', + effective: '2026-09-05T13:00:00+00:00', + // upstream's `EventSummary` reads these; without them it throws + start: '2026-09-05T13:00:00+00:00', + end: '2026-09-05T16:00:00+00:00', + ...image(listing03), + }, + { + '@id': '/documents/annual-report-2025', + '@type': 'File', + id: 'annual-report-2025', + title: 'Annual report 2025', + head_title: 'Publications', + description: + 'The full year in review, including the audited accounts and the service performance figures.', + effective: '2026-06-18T08:00:00+00:00', + ...image(listing04), + }, + { + '@id': '/news/library-reopens-after-refurbishment', + '@type': 'News Item', + id: 'library-reopens-after-refurbishment', + title: 'Library reopens after refurbishment', + head_title: 'Culture', + description: + 'Longer opening hours, a new study area and a lift to the first floor.', + effective: '2026-05-22T11:15:00+00:00', + ...image(listing05), + }, + { + '@id': '/documents/how-to-request-a-service', + '@type': 'Document', + id: 'how-to-request-a-service', + title: 'How to request a service', + head_title: 'Guides', + description: + 'The three routes available, what each one needs from you, and the expected turnaround.', + effective: '2026-04-09T16:45:00+00:00', + ...image(listing06), + }, +]; + +/** + * Items carrying a `videoUrl`, which is what `MediaCarouselTemplate` switches + * on to render an embed rather than an image. + */ +export const mediaItems: ListingItem[] = [ + { + ...listingItems[0], + '@id': '/media/council-meeting-recording', + title: 'Council meeting, full recording', + videoUrl: 'https://www.youtube.com/watch?v=aqz-KE-bpKQ', + }, + { + ...listingItems[2], + '@id': '/media/accessibility-workshop-recap', + title: 'Accessibility workshop, recap', + videoUrl: 'https://www.youtube.com/watch?v=ScMzIvxBSi4', + }, + { + ...listingItems[4], + '@id': '/media/library-tour', + title: 'A tour of the refurbished library', + }, +]; + +/** A single item, for isolating the one-card layouts. */ +export const singleItem: ListingItem[] = [listingItems[0]]; diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/Listing/messages.ts b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/messages.ts new file mode 100644 index 0000000..fbd2da8 --- /dev/null +++ b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/messages.ts @@ -0,0 +1,50 @@ +import { defineMessages } from 'react-intl'; + +/** + * Accessible labels shared by the carousel listing templates. + * + * `CarouselTemplate` and `MediaCarouselTemplate` render the same control set, so + * their labels live here rather than being declared twice. + */ +export const carouselMessages = defineMessages({ + carousel: { + id: 'Carousel', + defaultMessage: 'Carousel', + }, + mediaCarousel: { + id: 'Media Carousel', + defaultMessage: 'Media Carousel', + }, + controls: { + id: 'Carousel controls', + defaultMessage: 'Carousel controls', + }, + previous: { + id: 'Previous', + defaultMessage: 'Previous', + }, + next: { + id: 'Next', + defaultMessage: 'Next', + }, + items: { + id: 'Items', + defaultMessage: 'Items', + }, + goToItem: { + id: 'Go to item {index}', + defaultMessage: 'Go to item {index}', + }, + pause: { + id: 'Pause carousel', + defaultMessage: 'Pause carousel', + }, + play: { + id: 'Play carousel', + defaultMessage: 'Play carousel', + }, + video: { + id: 'Video', + defaultMessage: 'Video', + }, +}); diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/Listing/schema.ts b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/schema.ts index 548fd83..9a518ff 100644 --- a/frontend/packages/volto-light-theme/src/components/Blocks/Listing/schema.ts +++ b/frontend/packages/volto-light-theme/src/components/Blocks/Listing/schema.ts @@ -1,50 +1,75 @@ import { defineMessages } from 'react-intl'; +import type { IntlShape } from '@plone/types/src/i18n'; const messages = defineMessages({ + carousel: { + id: 'Carousel', + defaultMessage: 'Carousel', + }, carouselMaxHeight: { - id: 'Altura (px)', - defaultMessage: 'Altura (px)', + id: 'Height (px)', + defaultMessage: 'Height (px)', }, carouselObjectFit: { - id: 'Ajuste da mídia (object-fit)', - defaultMessage: 'Ajuste da mídia (object-fit)', + id: 'Media fit (object-fit)', + defaultMessage: 'Media fit (object-fit)', }, carouselObjectPosition: { - id: 'Posição (object-position)', - defaultMessage: 'Posição (object-position)', + id: 'Position (object-position)', + defaultMessage: 'Position (object-position)', }, carouselAutoPlay: { - id: 'Auto-avanço', - defaultMessage: 'Auto-avanço', + id: 'Auto-advance', + defaultMessage: 'Auto-advance', }, carouselAutoPlayInterval: { - id: 'Intervalo do auto-avanço (ms)', - defaultMessage: 'Intervalo do auto-avanço (ms)', + id: 'Auto-advance interval (ms)', + defaultMessage: 'Auto-advance interval (ms)', }, headlineButtonText: { - id: 'headlineButtonText', - defaultMessage: 'Texto do Botão (Título)', + id: 'Headline button text', + defaultMessage: 'Headline button text', }, headlineButtonLink: { - id: 'headlineButtonLink', - defaultMessage: 'Link do Botão (Título)', + id: 'Headline button link', + defaultMessage: 'Headline button link', }, gridColumns: { - id: 'gridColumns', - defaultMessage: 'Número de Colunas (Grid)', + id: 'Number of columns', + defaultMessage: 'Number of columns', + }, + columnCount: { + id: '{count, plural, one {# column} other {# columns}}', + defaultMessage: '{count, plural, one {# column} other {# columns}}', + }, + align: { + id: 'Alignment', + defaultMessage: 'Alignment', }, + positionLeftTop: { id: 'Left top', defaultMessage: 'Left top' }, + positionCenterTop: { id: 'Center top', defaultMessage: 'Center top' }, + positionRightTop: { id: 'Right top', defaultMessage: 'Right top' }, + positionLeftCenter: { id: 'Left center', defaultMessage: 'Left center' }, + positionCenter: { id: 'Center', defaultMessage: 'Center' }, + positionRightCenter: { id: 'Right center', defaultMessage: 'Right center' }, + positionLeftBottom: { id: 'Left bottom', defaultMessage: 'Left bottom' }, + positionCenterBottom: { + id: 'Center bottom', + defaultMessage: 'Center bottom', + }, + positionRightBottom: { id: 'Right bottom', defaultMessage: 'Right bottom' }, }); -const objectPositionChoices = [ - ['left top', 'Topo esquerdo'], - ['center top', 'Topo centro'], - ['right top', 'Topo direito'], - ['left center', 'Centro esquerdo'], - ['center center', 'Centro'], - ['right center', 'Centro direito'], - ['left bottom', 'Fundo esquerdo'], - ['center bottom', 'Fundo centro'], - ['right bottom', 'Fundo direito'], +const objectPositionChoices = (intl: IntlShape) => [ + ['left top', intl.formatMessage(messages.positionLeftTop)], + ['center top', intl.formatMessage(messages.positionCenterTop)], + ['right top', intl.formatMessage(messages.positionRightTop)], + ['left center', intl.formatMessage(messages.positionLeftCenter)], + ['center center', intl.formatMessage(messages.positionCenter)], + ['right center', intl.formatMessage(messages.positionRightCenter)], + ['left bottom', intl.formatMessage(messages.positionLeftBottom)], + ['center bottom', intl.formatMessage(messages.positionCenterBottom)], + ['right bottom', intl.formatMessage(messages.positionRightBottom)], ]; export const carouselSchemaEnhancer = ({ schema, formData, intl }) => { @@ -72,7 +97,7 @@ export const carouselSchemaEnhancer = ({ schema, formData, intl }) => { title: intl.formatMessage(messages.carouselObjectPosition), type: 'string', default: 'center center', - choices: objectPositionChoices, + choices: objectPositionChoices(intl), }, carouselAutoPlay: { title: intl.formatMessage(messages.carouselAutoPlay), @@ -94,7 +119,7 @@ export const carouselSchemaEnhancer = ({ schema, formData, intl }) => { if (!hasFieldset) { fieldsets.push({ id: 'carousel', - title: 'Carrossel', + title: intl.formatMessage(messages.carousel), fields: [ 'carouselMaxHeight', 'carouselObjectFit', @@ -153,7 +178,7 @@ export const mediaCarouselSchemaEnhancer = ({ schema, formData, intl }) => { title: intl.formatMessage(messages.carouselObjectPosition), type: 'string', default: 'center center', - choices: objectPositionChoices, + choices: objectPositionChoices(intl), }, carouselAutoPlay: { title: intl.formatMessage(messages.carouselAutoPlay), @@ -175,7 +200,7 @@ export const mediaCarouselSchemaEnhancer = ({ schema, formData, intl }) => { if (!hasFieldset) { fieldsets.push({ id: 'carousel', - title: 'Carrossel', + title: intl.formatMessage(messages.carousel), fields: [ 'carouselMaxHeight', 'carouselObjectFit', @@ -225,19 +250,17 @@ export const listingSchemaEnhancer = ({ schema, formData, intl }) => { gridColumns: { title: intl.formatMessage(messages.gridColumns), type: 'number', - choices: [ - [1, '1 Coluna'], - [2, '2 Colunas'], - [3, '3 Colunas'], - [4, '4 Colunas'], - ], + choices: [1, 2, 3, 4].map((count) => [ + count, + intl.formatMessage(messages.columnCount, { count }), + ]), default: 2, }, }; const fieldsets = schema.fieldsets || []; - // Adiciona ao default fieldset + // Add to the default fieldset const defaultFieldset = fieldsets.find((f) => f.id === 'default'); if (defaultFieldset) { if (!defaultFieldset.fields.includes('headlineButtonText')) { @@ -245,8 +268,8 @@ export const listingSchemaEnhancer = ({ schema, formData, intl }) => { } } - // Se a variação for grid (imageGallery é o nome interno de grid no Volto Light Theme e padrão), adiciona o gridColumns - // Vamos adicionar globalmente no 'default' e ignorar se não for usado, ou checar a variação: + // `imageGallery` is the internal id Volto Light Theme uses for its grid + // variation, so both ids get the column control. if ( formData?.variation === 'imageGallery' || formData?.variation === 'grid' @@ -266,13 +289,13 @@ export const teaserSchemaEnhancer = ({ schema, formData, intl }) => { ...schema.properties, align: { widget: 'align', - title: 'Alinhamento', + title: intl.formatMessage(messages.align), actions: ['left', 'right', 'center'], default: 'left', }, }; - // Se o schema já tiver fieldset styles, adiciona lá, senão no default + // Prefer the styling fieldset when the schema already has one. const hasStyles = schema.fieldsets?.some((f) => f.id === 'styling'); if (!hasStyles) { @@ -286,7 +309,7 @@ export const teaserSchemaEnhancer = ({ schema, formData, intl }) => { if (schema.properties?.styles?.schema) { schema.properties.styles.schema.properties.align = { widget: 'align', - title: 'Alinhamento', + title: intl.formatMessage(messages.align), actions: ['left', 'right', 'center'], default: 'left', }; diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/MainImageBlock/MainImageBlock.stories.tsx b/frontend/packages/volto-light-theme/src/components/Blocks/MainImageBlock/MainImageBlock.stories.tsx index 5e29fcc..dd205a9 100644 --- a/frontend/packages/volto-light-theme/src/components/Blocks/MainImageBlock/MainImageBlock.stories.tsx +++ b/frontend/packages/volto-light-theme/src/components/Blocks/MainImageBlock/MainImageBlock.stories.tsx @@ -3,17 +3,18 @@ import type { Decorator, Meta, StoryObj } from '@storybook/react'; import Wrapper from '@plone/volto/storybook'; import Layout from './Layout'; +import mainImage from '../../../storybook/images/main-image.jpg'; type StoryParams = { containerWidth?: number; }; -const SAMPLE_IMAGE = 'https://picsum.photos/seed/cm-uberlandia/1200/600'; +const SAMPLE_IMAGE = mainImage; const sampleImage = ( Plenário da Câmara Municipal = ({ return isEditMode ? (
) : null; diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/MainImageBlock/index.ts b/frontend/packages/volto-light-theme/src/components/Blocks/MainImageBlock/index.ts index b893ee2..20acec3 100644 --- a/frontend/packages/volto-light-theme/src/components/Blocks/MainImageBlock/index.ts +++ b/frontend/packages/volto-light-theme/src/components/Blocks/MainImageBlock/index.ts @@ -16,7 +16,7 @@ export interface MainImageBlockData { const MainImageBlockInfo: BlockConfigBase = { id: 'mainImageBlock', - title: 'Imagem Principal', + title: 'Main Image', icon: imageSVG, group: 'media', view: MainImageBlockView, diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/MainImageBlock/schema.tsx b/frontend/packages/volto-light-theme/src/components/Blocks/MainImageBlock/schema.tsx index 25e49aa..1bbe28b 100644 --- a/frontend/packages/volto-light-theme/src/components/Blocks/MainImageBlock/schema.tsx +++ b/frontend/packages/volto-light-theme/src/components/Blocks/MainImageBlock/schema.tsx @@ -6,48 +6,48 @@ import { defaultStylingSchema } from '../schema'; const messages = defineMessages({ mainImageBlock: { - id: 'Main Image Block', - defaultMessage: 'Bloco de Imagem Principal', + id: 'Main Image', + defaultMessage: 'Main Image', }, title: { id: 'Title', - defaultMessage: 'Título', + defaultMessage: 'Title', }, description: { id: 'Description', - defaultMessage: 'Descrição', + defaultMessage: 'Description', }, altText: { id: 'Alt Text', - defaultMessage: 'Descrição Alternativa', + defaultMessage: 'Alt Text', }, AltTextHint: { id: 'Alt text hint', - defaultMessage: 'Deixe em branco se a imagem for puramente decorativa', + defaultMessage: 'Leave empty if the image is purely decorative', }, AltTextHintLinkText: { id: 'Alt text hint link text', - defaultMessage: 'Descreva o propósito da imagem', + defaultMessage: 'Describe the purpose of the image', }, openLinkInNewTab: { id: 'Open in a new tab', - defaultMessage: 'Abrir em uma nova aba', + defaultMessage: 'Open in a new tab', }, align: { id: 'Alignment', - defaultMessage: 'Alinhamento', + defaultMessage: 'Alignment', }, size: { id: 'Image size', - defaultMessage: 'Tamanho', + defaultMessage: 'Image size', }, blockWidth: { id: 'Block Width', - defaultMessage: 'Largura do bloco', + defaultMessage: 'Block Width', }, styling: { id: 'Styling', - defaultMessage: 'Estilo', + defaultMessage: 'Styling', }, linkSettings: { id: 'Link settings', diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/QuoteBlock/QuoteBlock.stories.tsx b/frontend/packages/volto-light-theme/src/components/Blocks/QuoteBlock/QuoteBlock.stories.tsx index 25f9f82..1e6c8a9 100644 --- a/frontend/packages/volto-light-theme/src/components/Blocks/QuoteBlock/QuoteBlock.stories.tsx +++ b/frontend/packages/volto-light-theme/src/components/Blocks/QuoteBlock/QuoteBlock.stories.tsx @@ -35,7 +35,7 @@ const sampleSlateData = [ type: 'p', children: [ { - text: 'A comunicação é um direito humano fundamental. Sem ela, não há democracia plena nem exercício real da cidadania. Esta citação demonstra como o texto se comporta dentro do bloco, aplicando a fonte correta do Volto.', + text: 'Access to information is a fundamental right. Without it there is no full democracy and no real exercise of citizenship. This quotation shows how text behaves inside the block, with the theme typography applied.', }, ], }, @@ -46,7 +46,7 @@ export const DefaultTransparent: Story = { data: { '@type': 'quoteBlock', value: sampleSlateData, - author: 'Especialista em Comunicação', + author: 'Communications Specialist', backgroundStyle: 'transparent', }, }, @@ -57,7 +57,7 @@ export const FilledBackground: Story = { data: { '@type': 'quoteBlock', value: sampleSlateData, - author: 'Coletivo', + author: 'Collective', backgroundStyle: 'filled', }, }, diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/QuoteBlock/index.ts b/frontend/packages/volto-light-theme/src/components/Blocks/QuoteBlock/index.ts index d04da30..cf5bf50 100644 --- a/frontend/packages/volto-light-theme/src/components/Blocks/QuoteBlock/index.ts +++ b/frontend/packages/volto-light-theme/src/components/Blocks/QuoteBlock/index.ts @@ -13,7 +13,7 @@ export interface QuoteBlockData { const QuoteBlockInfo: BlockConfigBase = { id: 'quoteBlock', - title: 'Citação', + title: 'Quote', icon: quoteSVG, group: 'text', view: QuoteBlockView as any, diff --git a/frontend/packages/volto-light-theme/src/components/Blocks/QuoteBlock/schema.ts b/frontend/packages/volto-light-theme/src/components/Blocks/QuoteBlock/schema.ts index ff0d221..277e6e9 100644 --- a/frontend/packages/volto-light-theme/src/components/Blocks/QuoteBlock/schema.ts +++ b/frontend/packages/volto-light-theme/src/components/Blocks/QuoteBlock/schema.ts @@ -4,28 +4,24 @@ import { defaultStylingSchema } from '../schema'; const messages = defineMessages({ quoteBlock: { - id: 'quoteBlock', - defaultMessage: 'Bloco de Citação', + id: 'Quote', + defaultMessage: 'Quote', }, author: { - id: 'quoteAuthor', - defaultMessage: 'Autor / Fonte', + id: 'Author / Source', + defaultMessage: 'Author / Source', }, backgroundStyle: { - id: 'quoteBackgroundStyle', - defaultMessage: 'Estilo do fundo', + id: 'Background style', + defaultMessage: 'Background style', }, backgroundTransparent: { - id: 'quoteBackgroundTransparent', - defaultMessage: 'Transparente', + id: 'Transparent', + defaultMessage: 'Transparent', }, backgroundFilled: { - id: 'quoteBackgroundFilled', - defaultMessage: 'Preenchido', - }, - styling: { - id: 'Styling', - defaultMessage: 'Estilo', + id: 'Filled', + defaultMessage: 'Filled', }, }); diff --git a/frontend/packages/volto-light-theme/src/components/Controlpanels/Themes/fixtures.ts b/frontend/packages/volto-light-theme/src/components/Controlpanels/Themes/fixtures.ts index df3b864..9c782bc 100644 --- a/frontend/packages/volto-light-theme/src/components/Controlpanels/Themes/fixtures.ts +++ b/frontend/packages/volto-light-theme/src/components/Controlpanels/Themes/fixtures.ts @@ -12,13 +12,13 @@ export const defaultTheme: ThemeItem = { id: 'default', name: 'Default', description: 'The theme shipped with sc.voltolighttheme.', - primary_color: '#ffffff', - primary_foreground_color: '#000000', + primary_color_light: '#ffffff', + primary_foreground_color_light: '#000000', header_foreground_color: '#000000', - secondary_color: '#000000', - secondary_foreground_color: '#ffffff', - accent_color: '#f4822c', - accent_foreground_color: '#000000', + secondary_color_light: '#000000', + secondary_foreground_color_light: '#ffffff', + accent_color_light: '#f4822c', + accent_foreground_color_light: '#000000', }; export const corporateTheme: ThemeItem = { @@ -26,13 +26,13 @@ export const corporateTheme: ThemeItem = { id: 'corporate', name: 'Corporate', description: 'Blues, for a public-facing site.', - primary_color: '#123456', - primary_foreground_color: '#ffffff', + primary_color_light: '#123456', + primary_foreground_color_light: '#ffffff', header_foreground_color: '#ffffff', - secondary_color: '#1b3a5c', - secondary_foreground_color: '#ffffff', - accent_color: '#4a90d9', - accent_foreground_color: '#000000', + secondary_color_light: '#1b3a5c', + secondary_foreground_color_light: '#ffffff', + accent_color_light: '#4a90d9', + accent_foreground_color_light: '#000000', }; export const natalTheme: ThemeItem = { @@ -40,13 +40,13 @@ export const natalTheme: ThemeItem = { id: 'natal', name: 'Natal', description: 'Um tema festivo com tipografia Inter e bordas arredondadas.', - primary_color: '#d32f2f', - primary_foreground_color: '#ffffff', + primary_color_light: '#d32f2f', + primary_foreground_color_light: '#ffffff', header_foreground_color: '#ffffff', - secondary_color: '#2e7d32', - secondary_foreground_color: '#ffffff', - accent_color: '#fbc02d', - accent_foreground_color: '#000000', + secondary_color_light: '#2e7d32', + secondary_foreground_color_light: '#ffffff', + accent_color_light: '#fbc02d', + accent_foreground_color_light: '#000000', }; export const themes: ThemeItem[] = [defaultTheme, corporateTheme, natalTheme]; @@ -66,15 +66,20 @@ export const schema: JsonSchema = { properties: { name: { title: 'Theme name', type: 'string' }, description: { title: 'Description', type: 'string' }, - primary_color: { title: 'Primary colour', type: 'string' }, - accent_color: { title: 'Accent colour', type: 'string' }, + primary_color_light: { title: 'Primary colour', type: 'string' }, + accent_color_light: { title: 'Accent colour', type: 'string' }, }, required: ['name'], fieldsets: [ { id: 'default', title: 'Default', - fields: ['name', 'description', 'primary_color', 'accent_color'], + fields: [ + 'name', + 'description', + 'primary_color_light', + 'accent_color_light', + ], }, ], }; diff --git a/frontend/packages/volto-light-theme/src/components/DocumentByLine/DocumentByLine.tsx b/frontend/packages/volto-light-theme/src/components/DocumentByLine/DocumentByLine.tsx index d8439fd..2a2f31c 100644 --- a/frontend/packages/volto-light-theme/src/components/DocumentByLine/DocumentByLine.tsx +++ b/frontend/packages/volto-light-theme/src/components/DocumentByLine/DocumentByLine.tsx @@ -40,31 +40,28 @@ const DocumentByLine = ({ const displayPublished = showPublished && Boolean(effective); return ( - {displayPublished && ( - - - : - - )} - {displayModified && ( - - - : - - )} {displayAuthor && ( - - :{' '} +

+ :{' '} {authors && authors.map((author, i) => ( {author.fullname} ))} - +

+ )} + {displayPublished && ( +

+ + : +

+ )} + {displayModified && ( +

+ + : +

)}
); diff --git a/frontend/packages/volto-light-theme/src/components/DropdownMenu/DropdownMenu.stories.tsx b/frontend/packages/volto-light-theme/src/components/DropdownMenu/DropdownMenu.stories.tsx index 7cb8002..4f038bd 100644 --- a/frontend/packages/volto-light-theme/src/components/DropdownMenu/DropdownMenu.stories.tsx +++ b/frontend/packages/volto-light-theme/src/components/DropdownMenu/DropdownMenu.stories.tsx @@ -37,9 +37,9 @@ export const Default: Story = { title: 'Portais', LinkComponent: MockLink, links: [ - { title: 'Portal da Transparência', href: '/transparencia' }, + { title: 'Transparency Portal', href: '/transparency' }, { title: 'Dados Abertos', href: '/dados' }, - { title: 'Diário Oficial', href: '/diario-oficial' }, + { title: 'Official Gazette', href: '/official-gazette' }, ], }, }; @@ -70,7 +70,13 @@ export const CustomChildren: Story = { args: { title: 'Custom Content', children: ( -
+
Hello! This is arbitrary React content inside the dropdown panel.
diff --git a/frontend/packages/volto-light-theme/src/components/Header/MobileHeader/MobileTools.tsx b/frontend/packages/volto-light-theme/src/components/Header/MobileHeader/MobileTools.tsx index 58e9120..3479183 100644 --- a/frontend/packages/volto-light-theme/src/components/Header/MobileHeader/MobileTools.tsx +++ b/frontend/packages/volto-light-theme/src/components/Header/MobileHeader/MobileTools.tsx @@ -1,15 +1,24 @@ import React, { useState, useRef, useCallback } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; import config from '@plone/volto/registry'; import Icon from '@plone/volto/components/theme/Icon/Icon'; import moreIcon from '@plone/volto/icons/more.svg'; import HeaderBarActions from '../../HeaderBar/HeaderBarActions'; import useClickOutside from '../../../hooks/useClickOutside'; +const messages = defineMessages({ + accessibilityOptions: { + id: 'Accessibility options', + defaultMessage: 'Accessibility options', + }, +}); + type MobileToolsProps = { token?: string | null; }; const MobileTools: React.FC = ({ token }) => { + const intl = useIntl(); const [isOpen, setIsOpen] = useState(false); const dropdownRef = useRef(null); @@ -18,7 +27,7 @@ const MobileTools: React.FC = ({ token }) => { const close = useCallback(() => setIsOpen(false), []); - // Fecha ao clicar fora ou pressionar Escape + // Close on outside click or Escape useClickOutside(dropdownRef, close, isOpen); return display ? ( @@ -27,7 +36,7 @@ const MobileTools: React.FC = ({ token }) => { type="button" className="mobile-tools-button" onClick={() => setIsOpen(!isOpen)} - aria-label="Opções de Acessibilidade" + aria-label={intl.formatMessage(messages.accessibilityOptions)} > diff --git a/frontend/packages/volto-light-theme/src/components/HeaderBar/HeaderBar.tsx b/frontend/packages/volto-light-theme/src/components/HeaderBar/HeaderBar.tsx index 1ad478a..4a02609 100644 --- a/frontend/packages/volto-light-theme/src/components/HeaderBar/HeaderBar.tsx +++ b/frontend/packages/volto-light-theme/src/components/HeaderBar/HeaderBar.tsx @@ -30,7 +30,7 @@ type RootState = { const messages = defineMessages({ accessibilityMenu: { id: 'headerBarAccessibilityMenu', - defaultMessage: 'Menu de acessibilidade', + defaultMessage: 'Accessibility menu', }, menuLabel: { id: 'headerBarMenuLabel', diff --git a/frontend/packages/volto-light-theme/src/components/HeaderBar/HeaderBarActionsUI.stories.tsx b/frontend/packages/volto-light-theme/src/components/HeaderBar/HeaderBarActionsUI.stories.tsx index 68c770f..df2d9e3 100644 --- a/frontend/packages/volto-light-theme/src/components/HeaderBar/HeaderBarActionsUI.stories.tsx +++ b/frontend/packages/volto-light-theme/src/components/HeaderBar/HeaderBarActionsUI.stories.tsx @@ -22,7 +22,7 @@ const MockLanguageSelector = () => ( const MockAnontools = () => ( ); @@ -30,6 +30,7 @@ const MockAnontools = () => ( const meta: Meta = { title: 'Components/HeaderBarActions', component: HeaderBarActionsUI, + parameters: { fullBleed: true }, decorators: [ (Story) => (
= { padding: '2rem', display: 'flex', justifyContent: 'flex-end', - background: '#e0e0e0', + // the page ground the bar sits on, so the story follows the colour + // mode instead of being pinned to one grey + background: 'var(--primary-color)', }} >
@@ -63,8 +66,8 @@ type Story = StoryObj; export const Default: Story = { args: { quickLinks: [ - { label: 'Ouvidoria', href: '/ouvidoria' }, - { label: 'Acesso à Informação', href: '/acesso-a-informacao' }, + { label: 'Ombudsman', href: '/ombudsman' }, + { label: 'Freedom of Information', href: '/freedom-of-information' }, ], features: { languageSelector: true, diff --git a/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/MenuItem.stories.tsx b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/MenuItem.stories.tsx new file mode 100644 index 0000000..43e915f --- /dev/null +++ b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/MenuItem.stories.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import type { Decorator, Meta, StoryObj } from '@storybook/react'; +import Wrapper from '@plone/volto/storybook'; + +import MenuItem from './MenuItem'; +import { NavigationCanvas } from './storyDecorators'; +import { aboutUs, contact } from './mocks'; + +/** + * One top-level entry in the navigation bar. + * + * `hasFatMenu` decides whether it renders a button that opens a panel or a plain + * link. `MenuItem` is an `
  • `, so the canvas nests it in the `ul.desktop-menu` + * `_navigation.scss` expects. + */ +const withMenu: Decorator = (Story) => ( + + +
      + +
    +
    +
    +); + +const meta = { + title: 'Public/Navigation/MenuItem', + component: MenuItem, + decorators: [withMenu], + parameters: { layout: 'fullscreen' }, + tags: ['autodocs'], + args: { + index: 0, + pathname: '/', + lang: 'en', + desktopMenuOpen: null, + openMenu: () => {}, + closeMenu: () => {}, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +/** A section with children: renders the fat-menu trigger. */ +export const WithFatMenu: Story = { + args: { item: aboutUs, hasFatMenu: true }, +}; + +/** The same entry with its panel open. */ +export const FatMenuOpen: Story = { + args: { item: aboutUs, hasFatMenu: true, desktopMenuOpen: 0 }, +}; + +/** A leaf section: a plain link, no trigger. */ +export const PlainLink: Story = { + args: { item: contact, hasFatMenu: false }, +}; + +/** The current page, so the entry carries the active treatment. */ +export const Active: Story = { + args: { item: aboutUs, hasFatMenu: true, pathname: '/about-us' }, +}; diff --git a/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SCNavigation.stories.tsx b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SCNavigation.stories.tsx new file mode 100644 index 0000000..d5c442b --- /dev/null +++ b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SCNavigation.stories.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import type { Decorator, Meta, StoryObj } from '@storybook/react'; +import Wrapper from '@plone/volto/storybook'; + +import SCNavigation from './SCNavigation'; +import { NavigationGround } from './storyDecorators'; +import { navigationItems } from './mocks'; + +/** + * The site navigation, including the fat menu. + * + * `_navigation.scss` is scoped to `#navigation.navigation.scNavigation`, and + * `SCNavigation` emits all three itself — so this story only needs the themed + * ground behind it, not a second scope element. + * + * Items come from `state.navigation.items`, so the store is seeded rather than + * the items passed as a prop. + */ + +const store = { + navigation: { items: navigationItems }, + content: { data: { '@id': '/', '@type': 'Plone Site', title: 'Site' } }, +}; + +const withNavigation: Decorator = (Story) => ( + + + + + +); + +const meta = { + title: 'Public/Navigation/Navigation', + component: SCNavigation, + decorators: [withNavigation], + parameters: { layout: 'fullscreen', fullBleed: true }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +/** Nothing selected — the state a visitor sees on the front page. */ +export const Default: Story = { + args: { pathname: '/' }, +}; + +/** + * A section is current. `MenuItem` compares `pathname` against each item's + * `url` to mark the active trail. + */ +export const SectionActive: Story = { + args: { pathname: '/about-us' }, +}; + +/** A leaf inside a section, so the parent is active by prefix rather than exactly. */ +export const LeafActive: Story = { + args: { pathname: '/about-us/team' }, +}; + +/** A section with no children renders as a plain link, with no fat menu. */ +export const LeafSectionActive: Story = { + args: { pathname: '/contact' }, +}; diff --git a/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SubMenu.stories.tsx b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SubMenu.stories.tsx new file mode 100644 index 0000000..3ef8395 --- /dev/null +++ b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SubMenu.stories.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import type { Decorator, Meta, StoryObj } from '@storybook/react'; +import Wrapper from '@plone/volto/storybook'; + +import SubMenu from './SubMenu'; +import { NavigationCanvas } from './storyDecorators'; +import { aboutUs, ourWork, contact } from './mocks'; + +/** + * The fat menu that drops from a top-level section. + * + * `SubMenu` renders `.submenu-wrapper` itself, but `_navigation.scss` only + * reaches it through `#navigation.navigation`, so the canvas is required. The + * panel is shown when `desktopMenuOpen` matches the item's `index`, which is why + * the open state is a prop rather than something to click for. + */ + +const withNavigation: Decorator = (Story) => ( + + + {/* + `.submenu-wrapper` is `position: absolute; top: 100%` and the rule lives + under `.desktop-menu`, so the panel needs both that ancestor and the + `
  • ` MenuItem renders it inside — otherwise it positions against the + viewport and the close button lands in the wrong corner. + */} +
      + {/* + The panel is `left: 50%; width: 100vw; translateX(-50%)`, so it centres + on its own `
    • `. On a page that item sits mid-bar; a single item at + the left edge would throw the panel off to the right. + */} +
    • + {/* + The trigger MenuItem renders. Without it the `
    • ` has no width — + the panel is absolutely positioned — so the list collapses to zero + and the panel centres on the wrong point. + */} + About Us + +
    • +
    + + +); + +const meta = { + title: 'Public/Navigation/SubMenu', + component: SubMenu, + decorators: [withNavigation], + parameters: { layout: 'fullscreen', fullBleed: true }, + tags: ['autodocs'], + args: { + index: 0, + pathname: '/', + closeMenu: () => {}, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +/** Open, with two children — the ordinary fat-menu case. */ +export const Open: Story = { + args: { item: aboutUs, desktopMenuOpen: 0 }, +}; + +/** Closed. The panel stays in the DOM so the transition has something to animate. */ +export const Closed: Story = { + args: { item: aboutUs, desktopMenuOpen: null }, +}; + +/** A single child, one of which carries an empty description. */ +export const SingleChild: Story = { + args: { item: ourWork, desktopMenuOpen: 0 }, +}; + +/** A leaf section: no children, so the panel has only its header. */ +export const NoChildren: Story = { + args: { item: contact, desktopMenuOpen: 0 }, +}; + +/** The current page sits inside this section, so a child is marked active. */ +export const WithActiveChild: Story = { + args: { item: aboutUs, desktopMenuOpen: 0, pathname: '/about-us/team' }, +}; diff --git a/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SubMenuItem.stories.tsx b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SubMenuItem.stories.tsx new file mode 100644 index 0000000..55ad2ef --- /dev/null +++ b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SubMenuItem.stories.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import type { Decorator, Meta, StoryObj } from '@storybook/react'; +import Wrapper from '@plone/volto/storybook'; + +import SubMenuItem from './SubMenuItem'; +import { NavigationCanvas } from './storyDecorators'; +import { mission, aboutUs } from './mocks'; + +/** + * A single link inside a fat menu — title, optional description, and the active + * treatment when it is the current page. + */ +const withPanel: Decorator = (Story) => ( + + +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +); + +const meta = { + title: 'Public/Navigation/SubMenuItem', + component: SubMenuItem, + decorators: [withPanel], + parameters: { layout: 'fullscreen' }, + tags: ['autodocs'], + args: { pathname: '/', closeMenu: () => {} }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { args: { item: mission } }; + +export const Active: Story = { + args: { item: mission, pathname: '/about-us/mission' }, +}; + +export const WithoutDescription: Story = { + args: { item: { ...mission, description: '' } }, +}; + +/** A long title and description, to show the wrapping. */ +export const LongText: Story = { + args: { item: { ...aboutUs, items: [] } }, +}; diff --git a/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SubMenuItems.stories.tsx b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SubMenuItems.stories.tsx new file mode 100644 index 0000000..b3a6c59 --- /dev/null +++ b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SubMenuItems.stories.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import type { Decorator, Meta, StoryObj } from '@storybook/react'; +import Wrapper from '@plone/volto/storybook'; + +import SubMenuItems from './SubMenuItems'; +import { NavigationCanvas } from './storyDecorators'; +import { aboutUs, navigationItems } from './mocks'; + +/** + * The grid of links inside a fat menu. + * + * Rendered inside `.submenu-content`, which is where `_navigation.scss` expects + * `.submenu-items` to sit; without that ancestor the grid collapses to a stack. + */ +const withPanel: Decorator = (Story) => ( + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +); + +const meta = { + title: 'Public/Navigation/SubMenuItems', + component: SubMenuItems, + decorators: [withPanel], + parameters: { layout: 'fullscreen' }, + tags: ['autodocs'], + args: { pathname: '/', closeMenu: () => {} }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +/** Two items, both with descriptions. */ +export const TwoItems: Story = { args: { items: aboutUs.items ?? [] } }; + +/** One item whose description is empty, to show the layout without it. */ +export const MissingDescription: Story = { + args: { items: [{ ...aboutUs.items![0], description: '' }] }, +}; + +/** Enough items to wrap onto a second row. */ +export const ManyItems: Story = { + args: { + items: [...(aboutUs.items ?? []), ...navigationItems].slice(0, 5), + }, +}; + +/** One of the links is the current page. */ +export const WithActive: Story = { + args: { items: aboutUs.items ?? [], pathname: '/about-us/team' }, +}; diff --git a/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SubMenuSection.stories.tsx b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SubMenuSection.stories.tsx new file mode 100644 index 0000000..d0a6fb9 --- /dev/null +++ b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SubMenuSection.stories.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import type { Decorator, Meta, StoryObj } from '@storybook/react'; +import Wrapper from '@plone/volto/storybook'; + +import SubMenuSection from './SubMenuSection'; +import { NavigationCanvas } from './storyDecorators'; +import { aboutUs, ourWork, contact } from './mocks'; + +/** + * The header of a fat menu: a "Section" label, the section title as a link, and + * its description. + * + * The label is the one translatable string here — `Section`, via `defineMessages`. + */ +const withPanel: Decorator = (Story) => ( + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +); + +const meta = { + title: 'Public/Navigation/SubMenuSection', + component: SubMenuSection, + decorators: [withPanel], + parameters: { layout: 'fullscreen' }, + tags: ['autodocs'], + args: { closeMenu: () => {} }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const WithDescription: Story = { args: { item: aboutUs } }; + +export const ShortDescription: Story = { args: { item: ourWork } }; + +/** A long description, to show where it wraps. */ +export const LongDescription: Story = { args: { item: contact } }; + +export const WithoutDescription: Story = { + args: { item: { ...aboutUs, description: '' } }, +}; diff --git a/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SubMenuSection.tsx b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SubMenuSection.tsx index f516401..5896f28 100644 --- a/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SubMenuSection.tsx +++ b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/SubMenuSection.tsx @@ -9,8 +9,8 @@ type SubMenuSectionProps = { const messages = defineMessages({ section: { - id: 'section', - defaultMessage: 'Seção', + id: 'Section', + defaultMessage: 'Section', }, }); diff --git a/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/mocks.ts b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/mocks.ts index a92f7ac..fb4244f 100644 --- a/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/mocks.ts +++ b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/mocks.ts @@ -9,58 +9,54 @@ import type { NavigationItem } from './types'; * kept. Reuse these fixtures instead of hand-rolling data in each story. */ -/** "Sobre Nós" — a section with two children, exercises the fat-menu path. */ -export const sobreNos: NavigationItem = { - title: 'Sobre Nós', +/** "About Us" — a section with two children, exercises the fat-menu path. */ +export const aboutUs: NavigationItem = { + title: 'About Us', description: - 'Conheça quem somos, nossa missão e as pessoas que constroem a organização.', - url: '/sobre-nos', + 'Learn who we are, our mission and the people who build the organisation.', + url: '/about-us', items: [ { - title: 'Missão', - description: 'Nossa missão e valores fundamentais.', - url: '/sobre-nos/missao', + title: 'Mission', + description: 'Our mission and core values.', + url: '/about-us/mission', items: [], }, { - title: 'Equipe', - description: 'Conheça os membros da equipe.', - url: '/sobre-nos/equipe', + title: 'Team', + description: 'Meet the members of the team.', + url: '/about-us/team', items: [], }, ], }; -/** "Nossas ações" — a single child, one of which has an empty description. */ -export const nossasAcoes: NavigationItem = { - title: 'Nossas ações', - description: 'Ações e projetos da organização.', - url: '/nossas-acoes', +/** "Our Work" — a single child, whose description is empty. */ +export const ourWork: NavigationItem = { + title: 'Our Work', + description: 'Initiatives and projects run by the organisation.', + url: '/our-work', items: [ { - title: 'Notícias', + title: 'News', description: '', - url: '/nossas-acoes/noticias', + url: '/our-work/news', items: [], }, ], }; -/** "Contato" — a leaf item without children, renders as a plain link. */ -export const contato: NavigationItem = { - title: 'Contato', +/** "Contact" — a leaf item without children, renders as a plain link. */ +export const contact: NavigationItem = { + title: 'Contact', description: - 'Entre em contato conosco. Envie-nos uma mensagem identificando-se e explicando o assunto.', - url: '/contato', + 'Get in touch with us. Send a message introducing yourself and explaining the subject.', + url: '/contact', items: [], }; /** A single leaf child, handy for isolating SubMenuItem/SubMenuSection. */ -export const missao: NavigationItem = sobreNos.items![0]; +export const mission: NavigationItem = aboutUs.items![0]; /** The full top-level navigation, as consumed by `Navigation`. */ -export const navigationItems: NavigationItem[] = [ - sobreNos, - nossasAcoes, - contato, -]; +export const navigationItems: NavigationItem[] = [aboutUs, ourWork, contact]; diff --git a/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/storyDecorators.tsx b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/storyDecorators.tsx index 15765e9..011cca8 100644 --- a/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/storyDecorators.tsx +++ b/frontend/packages/volto-light-theme/src/components/Navigation/SCNavigation/storyDecorators.tsx @@ -17,9 +17,26 @@ const wrapperStyle: React.CSSProperties = { }; /** - * The container + navigation scope. `children` lets each story supply the - * inner markup (`ul.desktop-menu`, `.submenu-items`, ...) its component - * expects to be nested in. + * Just the themed ground, for `SCNavigation` itself — it renders its own + * `#navigation.navigation.scNavigation` element, so wrapping it in a second one + * would duplicate the id. + */ +export const NavigationGround = ({ + children, +}: { + children: React.ReactNode; +}) =>
    {children}
    ; + +/** + * The container + navigation scope, for the sub-components. `children` lets each + * story supply the inner markup (`ul.desktop-menu`, `.submenu-items`, ...) its + * component expects to be nested in. + * + * The class list matters: `_navigation.scss` nests everything under + * `#navigation.navigation` **and** `&.scNavigation`, so all three selectors have + * to be present. Without `scNavigation` the rules silently miss — the fat menu + * still renders its content, but `.submenu-items` never becomes the two-column + * grid and the links keep the browser's default colour. */ export const NavigationCanvas = ({ children, @@ -27,7 +44,11 @@ export const NavigationCanvas = ({ children: React.ReactNode; }) => (
    -
    diff --git a/frontend/packages/volto-light-theme/src/components/ThemeToggle/ThemeToggle.tsx b/frontend/packages/volto-light-theme/src/components/ThemeToggle/ThemeToggle.tsx index 8008028..7b1ea7a 100644 --- a/frontend/packages/volto-light-theme/src/components/ThemeToggle/ThemeToggle.tsx +++ b/frontend/packages/volto-light-theme/src/components/ThemeToggle/ThemeToggle.tsx @@ -3,9 +3,9 @@ import { defineMessages, useIntl } from 'react-intl'; import Icon from '@plone/volto/components/theme/Icon/Icon'; import Helmet from '@plone/volto/helpers/Helmet/Helmet'; -import contrastSVG from '../../icons/contrast.svg'; -import moonSVG from '../../icons/moon.svg'; -import sunSVG from '../../icons/sun.svg'; +import contrastSVG from '@simplesconsultoria/volto-light-theme/icons/contrast.svg'; +import moonSVG from '@simplesconsultoria/volto-light-theme/icons/moon.svg'; +import sunSVG from '@simplesconsultoria/volto-light-theme/icons/sun.svg'; import { applyTheme, getAppliedTheme, @@ -24,19 +24,19 @@ type ThemeOption = { const messages = defineMessages({ toggleTheme: { id: 'toggleTheme', - defaultMessage: 'Alternar tema', + defaultMessage: 'Toggle theme', }, lightTheme: { id: 'lightTheme', - defaultMessage: 'Claro', + defaultMessage: 'Light', }, darkTheme: { id: 'darkTheme', - defaultMessage: 'Escuro', + defaultMessage: 'Dark', }, highContrastTheme: { id: 'highContrastTheme', - defaultMessage: 'Alto contraste', + defaultMessage: 'High contrast', }, }); diff --git a/frontend/packages/volto-light-theme/src/config/blockThemes.ts b/frontend/packages/volto-light-theme/src/config/blockThemes.ts new file mode 100644 index 0000000..0c408db --- /dev/null +++ b/frontend/packages/volto-light-theme/src/config/blockThemes.ts @@ -0,0 +1,53 @@ +/** + * The block-theme factory. + * + * Kept apart from `config/blocks` deliberately: the mapping has no dependency on + * block registration, and importing it from there pulls in every registered block. + * `_root.scss` must declare a `--block-theme-{name}-*` token for each property this + * maps — a `var()` with no definition and no fallback is invalid at computed-value + * time, so the whole declaration is dropped. `theme/blockThemes.test.ts` guards it. + */ + +export interface ThemeDefinition { + style: Record; + name: string; + label: string; +} + +/** + * Create a complete theme definition by mapping every `--theme-*` variable + * to its `--block-theme-{name}-*` counterpart. + * + * @param name — Machine name used as the CSS token root (e.g. `"default"`, `"brand"`). + * @param label — Human-readable label shown in the editor color picker. + */ +export function createThemeDefinition( + name: string, + label: string, +): ThemeDefinition { + const v = (suffix: string) => `var(--block-theme-${name}-${suffix})`; + return { + style: { + // Ground + '--theme-color': v('bg'), + '--theme-foreground-color': v('text'), + '--theme-high-contrast-foreground-color': v('high-contrast'), + '--theme-low-contrast-foreground-color': v('low-contrast'), + '--theme-foreground-accent-color': v('accent-color'), + // High Ground + '--theme-high-contrast-color': v('high-bg'), + '--theme-top-foreground-color': v('top-text'), + '--theme-top-high-contrast-foreground-color': v('top-high-contrast'), + '--theme-top-low-contrast-foreground-color': v('top-low-contrast'), + '--theme-top-accent-color': v('top-accent-color'), + // Border + '--theme-border-color': v('border'), + '--theme-border-width': v('border-width'), + // Pattern (optional) + '--theme-pattern-image': v('pattern-image'), + '--theme-pattern-opacity': v('pattern-opacity'), + }, + name, + label, + }; +} diff --git a/frontend/packages/volto-light-theme/src/config/blocks.ts b/frontend/packages/volto-light-theme/src/config/blocks.ts index 18d9df4..b495e8b 100644 --- a/frontend/packages/volto-light-theme/src/config/blocks.ts +++ b/frontend/packages/volto-light-theme/src/config/blocks.ts @@ -1,3 +1,4 @@ +import { createThemeDefinition } from './blockThemes'; import type { ConfigType } from '@plone/registry'; import type { BlockConfigBase } from '@plone/types'; @@ -33,7 +34,7 @@ declare module '@plone/types' { // Each theme maps semantic `--theme-*` CSS custom properties to concrete // `--block-theme-{name}-*` tokens defined in `_root.scss`. // -// The structure follows a two-layer model: +// The structure follows a four-layer model: // // Ground — the block's own background & text. // High Ground — elevated elements within the block (cards, chips, etc.). @@ -41,13 +42,10 @@ declare module '@plone/types' { // Pattern — optional decorative background pattern / image. // // By using a factory, adding a new theme in a downstream project is a -// one-liner: `createThemeDefinition('purple', 'Roxo')` +// one-liner: `createThemeDefinition('purple', 'Purple')` -export interface ThemeDefinition { - style: Record; - name: string; - label: string; -} +export type { ThemeDefinition } from './blockThemes'; +export { createThemeDefinition } from './blockThemes'; /** * `themes` and `defaultTheme` are a kitconcept convention that Volto's own @@ -62,44 +60,6 @@ declare module '@plone/types' { } } -/** - * Create a complete theme definition by mapping every `--theme-*` variable - * to its `--block-theme-{name}-*` counterpart. - * - * @param name — Machine name used as the CSS token root (e.g. `"default"`, `"brand"`). - * @param label — Human-readable label shown in the editor color picker. - */ -export function createThemeDefinition( - name: string, - label: string, -): ThemeDefinition { - const v = (suffix: string) => `var(--block-theme-${name}-${suffix})`; - return { - style: { - // Ground - '--theme-color': v('bg'), - '--theme-foreground-color': v('text'), - '--theme-high-contrast-foreground-color': v('high-contrast'), - '--theme-low-contrast-foreground-color': v('low-contrast'), - '--theme-foreground-accent-color': v('accent-color'), - // High Ground - '--theme-high-contrast-color': v('high-bg'), - '--theme-top-foreground-color': v('top-text'), - '--theme-top-high-contrast-foreground-color': v('top-high-contrast'), - '--theme-top-low-contrast-foreground-color': v('top-low-contrast'), - '--theme-top-accent-color': v('top-accent-color'), - // Border - '--theme-border-color': v('border'), - '--theme-border-width': v('border-width'), - // Pattern (optional — defaults to none/0 in _root.scss) - '--theme-pattern-image': v('pattern-image'), - '--theme-pattern-opacity': v('pattern-opacity'), - }, - name, - label, - }; -} - const customThemes: ThemeDefinition[] = [ createThemeDefinition('default', 'Primary'), createThemeDefinition('brand', 'Brand'), @@ -192,7 +152,7 @@ export default function install(config: ConfigType) { ...variations, { id: 'carousel', - title: 'Carrossel', + title: 'Carousel', template: CarouselTemplate, schemaEnhancer: carouselSchemaEnhancer, }, @@ -206,7 +166,7 @@ export default function install(config: ConfigType) { ...variations, { id: 'mediaCarousel', - title: 'Carrossel de Mídia', + title: 'Media Carousel', template: MediaCarouselTemplate, schemaEnhancer: mediaCarouselSchemaEnhancer, }, @@ -218,7 +178,7 @@ export default function install(config: ConfigType) { ...variations, { id: 'teaser', - title: 'Destaque', + title: 'Highlight', template: TeaserTemplate, schemaEnhancer: teaserSchemaEnhancer, }, diff --git a/frontend/packages/volto-light-theme/src/helpers/themeStyles.test.ts b/frontend/packages/volto-light-theme/src/helpers/themeStyles.test.ts index 06e83e1..9901cb9 100644 --- a/frontend/packages/volto-light-theme/src/helpers/themeStyles.test.ts +++ b/frontend/packages/volto-light-theme/src/helpers/themeStyles.test.ts @@ -1,4 +1,6 @@ import { describe, expect, it } from 'vitest'; +import fs from 'node:fs'; +import path from 'node:path'; import { cssVariableFor, isSafeCssValue, @@ -15,26 +17,59 @@ const serialized = (partial: Partial): SerializedTheme => partial as SerializedTheme; /** - * Every style-bearing field of `ISCVLTThemeDefinition`, i.e. what - * `theme_settings()` returns. Pinned here so a field added on the backend - * without a matching token shows up as a failure rather than a silent no-op. + * Every colour field of `ISCVLTThemeDefinition`, i.e. what `theme_settings()` + * returns. Pinned here so a field added on the backend without a matching + * token shows up as a failure rather than a silent no-op; the tests in + * "stylesheet contract" below check this list against the schema itself and + * against the properties `_root.scss` actually reads. */ const BACKEND_SETTING_FIELDS = [ - 'accent_color', - 'accent_foreground_color', - 'header_foreground_color', - 'primary_color', - 'primary_foreground_color', - 'secondary_color', - 'secondary_foreground_color', + 'primary_color_light', + 'primary_color_dark', + 'primary_foreground_color_light', + 'primary_foreground_color_dark', + 'primary_low_foreground_color_light', + 'primary_low_foreground_color_dark', + 'primary_accent_color_light', + 'primary_accent_color_dark', + 'secondary_color_light', + 'secondary_color_dark', + 'secondary_foreground_color_light', + 'secondary_foreground_color_dark', + 'secondary_low_foreground_color_light', + 'secondary_low_foreground_color_dark', + 'secondary_accent_color_light', + 'secondary_accent_color_dark', + 'accent_color_light', + 'accent_color_dark', + 'accent_foreground_color_light', + 'accent_foreground_color_dark', + 'accent_low_foreground_color_light', + 'accent_low_foreground_color_dark', + 'accent_accent_color_light', + 'accent_accent_color_dark', + 'neutral_color_light', + 'neutral_color_dark', + 'neutral_foreground_color_light', + 'neutral_foreground_color_dark', + 'neutral_low_foreground_color_light', + 'neutral_low_foreground_color_dark', + 'neutral_accent_color_light', + 'neutral_accent_color_dark', + 'event_color_light', + 'event_color_dark', + 'file_color_light', + 'file_color_dark', + 'image_color_light', + 'image_color_dark', ] as const; describe('cssVariableFor', () => { it.each([ - ['primary_color', '--primary-color'], - ['primary_foreground_color', '--primary-foreground-color'], + ['primary_color_light', '--primary-color-light'], + ['primary_foreground_color_light', '--primary-foreground-color-light'], ['header_foreground_color', '--header-foreground-color'], - ['accent_color', '--accent-color'], + ['accent_color_dark', '--accent-color-dark'], ])('derives %o as %o', (field, variable) => { expect(cssVariableFor(field)).toBe(variable); }); @@ -48,25 +83,87 @@ describe('cssVariableFor', () => { }); }); +/** + * The two ends of the contract this file only ever checked in the middle. + * + * `cssVariableFor` deriving `--event-color-light` from `event_color_light` is + * worth nothing if no stylesheet reads that property. When `_root.scss` read + * `--event-color-override` instead, every test here still passed and the + * control panel's content-type colours did nothing at all. + */ +describe('stylesheet contract', () => { + const ROOT_SCSS = fs.readFileSync( + path.join(__dirname, '..', 'theme', '_root.scss'), + 'utf8', + ); + + /** Written as a regex so wrapping a long `light-dark()` still matches. */ + const isRead = (token: string) => + new RegExp(`var\\(\\s*${token}\\s*[,)]`).test(ROOT_SCSS); + + it('has _root.scss read the property every backend field derives', () => { + const unread = BACKEND_SETTING_FIELDS.filter( + (field) => !isRead(cssVariableFor(field)), + ); + + expect( + unread, + `_root.scss reads no such custom property, so setting ` + + `${unread.join(', ')} in the themes control panel changes nothing`, + ).toEqual([]); + }); + + const SCHEMA = path.join( + __dirname, + '..', + '..', + '..', + '..', + '..', + 'backend', + 'src', + 'sc', + 'voltolighttheme', + 'interfaces.py', + ); + + it.skipIf(!fs.existsSync(SCHEMA))( + 'pins exactly the colour fields the backend schema declares', + () => { + const declared = [ + ...fs + .readFileSync(SCHEMA, 'utf8') + .matchAll(/^ {4}(\w+) = fields\.Color\(/gm), + ].map((m) => m[1]); + + expect( + declared.length, + 'no Color fields found in interfaces.py', + ).toBeGreaterThan(0); + expect([...BACKEND_SETTING_FIELDS].sort()).toEqual(declared.sort()); + }, + ); +}); + describe('themeCustomProperties', () => { it('maps every setting onto its custom property', () => { const values = settings({ - primary_color: '#123456', - primary_foreground_color: '#ffffff', + primary_color_light: '#123456', + primary_foreground_color_light: '#ffffff', header_foreground_color: '#eeeeee', - secondary_color: '#000000', - secondary_foreground_color: '#fafafa', - accent_color: '#ffb703', - accent_foreground_color: '#000000', + secondary_color_dark: '#000000', + secondary_foreground_color_dark: '#fafafa', + accent_color_light: '#ffb703', + accent_foreground_color_dark: '#000000', }); expect(themeCustomProperties(values)).toEqual({ - '--primary-color': '#123456', - '--primary-foreground-color': '#ffffff', + '--primary-color-light': '#123456', + '--primary-foreground-color-light': '#ffffff', '--header-foreground-color': '#eeeeee', - '--secondary-color': '#000000', - '--secondary-foreground-color': '#fafafa', - '--accent-color': '#ffb703', - '--accent-foreground-color': '#000000', + '--secondary-color-dark': '#000000', + '--secondary-foreground-color-dark': '#fafafa', + '--accent-color-light': '#ffb703', + '--accent-foreground-color-dark': '#000000', }); }); @@ -80,21 +177,27 @@ describe('themeCustomProperties', () => { }); it('accepts the three-digit hex form', () => { - expect(themeCustomProperties(settings({ primary_color: '#abc' }))).toEqual({ - '--primary-color': '#abc', + expect( + themeCustomProperties(settings({ primary_color_light: '#abc' })), + ).toEqual({ + '--primary-color-light': '#abc', }); }); it('trims surrounding whitespace', () => { expect( - themeCustomProperties(settings({ primary_color: ' #123456 ' })), - ).toEqual({ '--primary-color': '#123456' }); + themeCustomProperties(settings({ primary_color_light: ' #123456 ' })), + ).toEqual({ + '--primary-color-light': '#123456', + }); }); it('omits fields the theme does not carry', () => { expect( - themeCustomProperties(settings({ primary_color: '#123456' })), - ).toEqual({ '--primary-color': '#123456' }); + themeCustomProperties(settings({ primary_color_light: '#123456' })), + ).toEqual({ + '--primary-color-light': '#123456', + }); }); it('returns nothing for a missing theme', () => { @@ -117,18 +220,20 @@ describe('themeCustomProperties', () => { '', 'var(--x)', ])('drops %o', (value) => { - expect(themeCustomProperties({ primary_color: value })).toEqual({}); + expect(themeCustomProperties({ primary_color_light: value })).toEqual({}); }); it('drops a value trying to close the declaration', () => { // The value is interpolated into a