diff --git a/.stylelintignore b/.stylelintignore index 4b2be235a0..f52d70d0db 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -2,3 +2,4 @@ dist docs node_modules packages/registry/lib +build diff --git a/PLIPs.md b/PLIPs.md new file mode 100644 index 0000000000..669bdccfb1 --- /dev/null +++ b/PLIPs.md @@ -0,0 +1,8 @@ +# Plone Improvement Proposals (PLIPs) + +For details of the PLIP process, read the following. + +- [Plone Improvement Proposals (PLIPs)](https://6.docs.plone.org/contributing/core/plips.html) +- [PLIP review](https://6.docs.plone.org/contributing/core/plip-review.html) + +You can also [browse the current list of open PLIPs for Volto](https://github.com/plone/volto/labels/03%20type%3A%20feature%20(plip)). diff --git a/packages/cmsui/.eslintrc.cjs b/packages/cmsui/.eslintrc.cjs new file mode 100644 index 0000000000..b3a71fc44c --- /dev/null +++ b/packages/cmsui/.eslintrc.cjs @@ -0,0 +1,11 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + overrides: [ + { + files: ['**/*.ts', '**/*.tsx'], + extends: [ + 'plugin:react/jsx-runtime', // We only want this for non-library code (eg. volto add-ons) + ], + }, + ], +}; diff --git a/packages/cmsui/.release-it.json b/packages/cmsui/.release-it.json new file mode 100644 index 0000000000..507f953edc --- /dev/null +++ b/packages/cmsui/.release-it.json @@ -0,0 +1,29 @@ +{ + "plugins": { + "../scripts/prepublish.js": {} + }, + "hooks": { + "after:bump": [ + "pipx run towncrier build --draft --yes --version ${version} > .changelog.draft", + "pipx run towncrier build --yes --version ${version}" + ], + "after:release": "rm .changelog.draft" + }, + "npm": { + "publish": false + }, + "git": { + "commitArgs": ["--no-verify"], + "changelog": "pipx run towncrier build --draft --yes --version 0.0.0", + "requireUpstream": false, + "requireCleanWorkingDir": false, + "commitMessage": "Release @plone/cmsui ${version}", + "tagName": "plone-cmsui-${version}", + "tagAnnotation": "Release @plone/cmsui ${version}" + }, + "github": { + "release": true, + "releaseName": "@plone/cmsui ${version}", + "releaseNotes": "cat .changelog.draft" + } +} diff --git a/packages/cmsui/.storybook/Logo.svg b/packages/cmsui/.storybook/Logo.svg new file mode 100644 index 0000000000..5a7ba56902 --- /dev/null +++ b/packages/cmsui/.storybook/Logo.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/cmsui/.storybook/main.ts b/packages/cmsui/.storybook/main.ts new file mode 100644 index 0000000000..cf9f4ddec3 --- /dev/null +++ b/packages/cmsui/.storybook/main.ts @@ -0,0 +1,41 @@ +import type { StorybookConfig } from '@storybook/react-vite'; +import { mergeConfig } from 'vite'; + +const config: StorybookConfig = { + // For some reason the property does not allow negation + // https://github.com/storybookjs/storybook/issues/11181#issuecomment-1535288804 + stories: [ + '../components/**/*.mdx', + '../components/**/*.stories.@(js|jsx|ts|tsx)', + ], + addons: [ + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-interactions', + ], + framework: { + name: '@storybook/react-vite', + options: {}, + }, + docs: { + autodocs: 'tag', + }, + typescript: { + reactDocgen: 'react-docgen-typescript', + reactDocgenTypescriptOptions: { + compilerOptions: { + allowSyntheticDefaultImports: false, + esModuleInterop: false, + }, + propFilter: () => true, + }, + }, + async viteFinal(config) { + return mergeConfig(config, { + build: { + minify: false, + }, + }); + }, +}; +export default config; diff --git a/packages/cmsui/.storybook/manager.js b/packages/cmsui/.storybook/manager.js new file mode 100644 index 0000000000..2e62084432 --- /dev/null +++ b/packages/cmsui/.storybook/manager.js @@ -0,0 +1,6 @@ +import { addons } from '@storybook/manager-api'; +import theme from './theme'; + +addons.setConfig({ + theme, +}); diff --git a/packages/cmsui/.storybook/preview-head.html b/packages/cmsui/.storybook/preview-head.html new file mode 100644 index 0000000000..05da1e9dfb --- /dev/null +++ b/packages/cmsui/.storybook/preview-head.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/cmsui/.storybook/preview.ts b/packages/cmsui/.storybook/preview.ts new file mode 100644 index 0000000000..362843c9bc --- /dev/null +++ b/packages/cmsui/.storybook/preview.ts @@ -0,0 +1,24 @@ +import './storybook-base.css'; +import '@plone/components/dist/basic.css'; +import '../main.css'; +import config from '@plone/registry'; +import installSlots from '../config'; +import installBlocks from '@plone/blocks'; + +config.set('slots', {}); +config.set('utilities', {}); +installSlots(config); +installBlocks(config); + +export const parameters = { + backgrounds: { + default: 'light', + }, + actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, +}; diff --git a/packages/cmsui/.storybook/storybook-base.css b/packages/cmsui/.storybook/storybook-base.css new file mode 100644 index 0000000000..f55ba4bf27 --- /dev/null +++ b/packages/cmsui/.storybook/storybook-base.css @@ -0,0 +1,19 @@ +/* Base styles */ +:root { + --basic-font-family: system-ui; + --basic-font-size: 16px; + background: var(--background-color); + font-family: var(--basic-font-family); + font-size: var(--basic-font-size); + line-height: 1.5; +} + +.sbdocs.sbdocs-content { + p { + font-size: 16px; + } +} + +#storybook-root { + width: 100vw; +} diff --git a/packages/cmsui/.storybook/theme.ts b/packages/cmsui/.storybook/theme.ts new file mode 100644 index 0000000000..3262e1f700 --- /dev/null +++ b/packages/cmsui/.storybook/theme.ts @@ -0,0 +1,10 @@ +import { create } from '@storybook/theming/create'; +import logo from './Logo.svg'; + +export default create({ + base: 'light', + brandTitle: '@plone/components StoryBook', + brandUrl: 'https://plone-components.netlify.app/', + brandImage: logo, + brandTarget: '_self', +}); diff --git a/packages/cmsui/.stylelintrc b/packages/cmsui/.stylelintrc new file mode 100644 index 0000000000..8ac62f8d0f --- /dev/null +++ b/packages/cmsui/.stylelintrc @@ -0,0 +1,14 @@ +{ + "extends": ["stylelint-config-idiomatic-order"], + "plugins": ["stylelint-prettier"], + "overrides": [ + { + "files": ["**/*.scss"], + "customSyntax": "postcss-scss" + } + ], + "rules": { + "prettier/prettier": true, + "order/properties-alphabetical-order": null + } +} diff --git a/packages/cmsui/CHANGELOG.md b/packages/cmsui/CHANGELOG.md new file mode 100644 index 0000000000..2969638257 --- /dev/null +++ b/packages/cmsui/CHANGELOG.md @@ -0,0 +1,11 @@ +# @plone/cmsui Release Notes + + + + + +## 1.0.0 (unreleased) diff --git a/packages/cmsui/README.md b/packages/cmsui/README.md new file mode 100644 index 0000000000..ee5d757cd6 --- /dev/null +++ b/packages/cmsui/README.md @@ -0,0 +1,8 @@ +# `@plone/cmsui` + +This package provides default structural slots for Plone 7 and the API-first story. + +> [!WARNING] +> This package or app is experimental. +> The community offers no support whatsoever for it. +> Breaking changes may occur without notice. diff --git a/packages/cmsui/components/Field/Field.tsx b/packages/cmsui/components/Field/Field.tsx new file mode 100644 index 0000000000..9e91e94eb0 --- /dev/null +++ b/packages/cmsui/components/Field/Field.tsx @@ -0,0 +1,185 @@ +import config from '@plone/registry'; +import type { + WidgetsConfigById, + WidgetsConfigByFactory, + WidgetsConfigByType, + WidgetsConfigByVocabulary, + WidgetsConfigByWidget, +} from '@plone/types'; + +type FieldProps = { + id: keyof WidgetsConfigById; + widget: keyof WidgetsConfigByWidget; + vocabulary: { '@id': keyof WidgetsConfigByVocabulary }; + choices: string; + type: keyof WidgetsConfigByType; + focus: boolean; + mode: string; + widgetOptions: any; + factory: keyof WidgetsConfigByFactory; + onChange: (id: string, value: any) => void; + placeholder: string; + title: string; +}; + +const MODE_HIDDEN = 'hidden'; //hidden mode. If mode is hidden, field is not rendered + +/** + * Get default widget + */ +const getWidgetDefault = (): React.ComponentType => config.widgets.default; + +/** + * Get widget by field's `id` attribute + */ +const getWidgetByFieldId = ( + id: FieldProps['id'], +): React.ComponentType | null => config.widgets.id[id] || null; + +/** + * Get widget by factory attribute + */ +const getWidgetByFactory = ( + factory: FieldProps['factory'], +): React.ComponentType | null => config.widgets.factory?.[factory] || null; + +/** + * Get widget by field's `widget` attribute + */ +const getWidgetByName = ( + widget: FieldProps['widget'], +): React.ComponentType | null => + typeof widget === 'string' + ? config.widgets.widget[widget] || getWidgetDefault() + : null; + +/** + * Get widget by tagged values + * + +directives.widget( + 'fieldname', + frontendOptions={ + "widget": 'specialwidget', + "version": 'extra' + }) + + */ +const getWidgetFromTaggedValues = (widgetOptions: { + frontendOptions: { widget: FieldProps['widget']; widgetProps: any }; +}): React.ComponentType | null => + typeof widgetOptions?.frontendOptions?.widget === 'string' + ? config.widgets.widget[widgetOptions.frontendOptions.widget] + : null; + +/** + * Get widget props from tagged values + * + +directives.widget( + "fieldname", + frontendOptions={ + "widget": "specialwidget", + "widgetProps": {"prop1": "specialprop"} + }) + + */ +const getWidgetPropsFromTaggedValues = (widgetOptions: { + frontendOptions: { widget: string; widgetProps: any }; +}): React.ComponentType | null => + typeof widgetOptions?.frontendOptions?.widgetProps === 'object' + ? widgetOptions.frontendOptions.widgetProps + : null; + +/** + * Get widget by field's `vocabulary` attribute + */ +const getWidgetByVocabulary = ( + vocabulary: FieldProps['vocabulary'], +): React.ComponentType | null => + vocabulary && vocabulary['@id'] + ? config.widgets.vocabulary[ + vocabulary['@id'].replace( + /^.*\/@vocabularies\//, + '', + ) as keyof WidgetsConfigByVocabulary + ] + : null; + +/** + * Get widget by field's hints `vocabulary` attribute in widgetOptions + */ +const getWidgetByVocabularyFromHint = ( + props: FieldProps, +): React.ComponentType | null => + props.widgetOptions && props.widgetOptions.vocabulary + ? config.widgets.vocabulary[ + props.widgetOptions.vocabulary['@id'].replace( + /^.*\/@vocabularies\//, + '', + ) as keyof WidgetsConfigByVocabulary + ] + : null; + +/** + * Get widget by field's `choices` attribute + */ +const getWidgetByChoices = ( + props: FieldProps, +): React.ComponentType | null => { + if (props.choices) { + return config.widgets.choices; + } + + if (props.vocabulary) { + // If vocabulary exists, then it means it's a choice field in disguise with + // no widget specified that probably contains a string then we force it + // to be a select widget instead + return config.widgets.choices; + } + + return null; +}; + +/** + * Get widget by field's `type` attribute + */ +const getWidgetByType = (type: FieldProps['type']): React.ComponentType => + config.widgets.type[type] || null; + +const Field = (props: FieldProps) => { + const Widget = + getWidgetByFieldId(props.id) || + getWidgetFromTaggedValues(props.widgetOptions) || + getWidgetByName(props.widget) || + getWidgetByChoices(props) || + getWidgetByVocabulary(props.vocabulary) || + getWidgetByVocabularyFromHint(props) || + getWidgetByFactory(props.factory) || + getWidgetByType(props.type) || + getWidgetDefault(); + + if (props.mode === MODE_HIDDEN) { + return null; + } + + // Adding the widget props from tagged values (if any) + const widgetProps = { + ...props, + label: props.title, + placeholder: props.placeholder || 'Type something...', + // Temporary translator from the old widget signature (id, value) to the new one (value) + onChange: (arg1: string, arg2: string | undefined) => { + if (!arg2 === undefined) { + props.onChange(props.id, arg1); + } else { + props.onChange(props.id, arg2); + } + }, + ...getWidgetPropsFromTaggedValues(props.widgetOptions), + }; + + return ; +}; + +export default Field; diff --git a/packages/cmsui/config/widgets.ts b/packages/cmsui/config/widgets.ts new file mode 100644 index 0000000000..3ff5ba82fb --- /dev/null +++ b/packages/cmsui/config/widgets.ts @@ -0,0 +1,9 @@ +import type { ConfigType } from '@plone/registry'; +import { QuantaTextAreaField, QuantaTextField } from '@plone/components'; + +export default function install(config: ConfigType) { + config.widgets.default = QuantaTextField; + config.widgets.widget.textarea = QuantaTextAreaField; + + return config; +} diff --git a/packages/cmsui/index.ts b/packages/cmsui/index.ts new file mode 100644 index 0000000000..4804a02ba9 --- /dev/null +++ b/packages/cmsui/index.ts @@ -0,0 +1,9 @@ +import type { ConfigType } from '@plone/registry'; +import installWidgets from './config/widgets'; +import '@plone/components/dist/basic.css'; +import '@plone/components/dist/quanta.css'; + +export default function install(config: ConfigType) { + installWidgets(config); + return config; +} diff --git a/packages/cmsui/main.css b/packages/cmsui/main.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/cmsui/news/.gitkeep b/packages/cmsui/news/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/cmsui/package.json b/packages/cmsui/package.json new file mode 100644 index 0000000000..d397a3f337 --- /dev/null +++ b/packages/cmsui/package.json @@ -0,0 +1,80 @@ +{ + "name": "@plone/cmsui", + "description": "Plone CMSUI components", + "maintainers": [ + { + "name": "Plone Foundation", + "url": "https://plone.org" + } + ], + "funding": "https://github.com/sponsors/plone", + "license": "MIT", + "version": "1.0.0", + "repository": { + "type": "git", + "url": "https://github.com/plone/volto.git" + }, + "bugs": { + "url": "https://github.com/plone/volto/issues" + }, + "homepage": "https://plone.org", + "keywords": [ + "volto", + "plone", + "plone6", + "react", + "helpers" + ], + "publishConfig": { + "access": "public" + }, + "type": "module", + "main": "index.ts", + "scripts": { + "test": "vitest", + "dry-release": "release-it --dry-run", + "release": "release-it", + "release-major-alpha": "release-it major --preRelease=alpha", + "release-alpha": "release-it --preRelease=alpha", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + }, + "dependencies": { + "@plone/client": "workspace:*", + "@plone/components": "workspace:*", + "@plone/registry": "workspace:*", + "react-aria-components": "^1.5.0" + }, + "devDependencies": { + "@plone/types": "workspace:*", + "@storybook/addon-essentials": "^8.0.4", + "@storybook/addon-interactions": "^8.0.4", + "@storybook/addon-links": "^8.0.4", + "@storybook/addon-mdx-gfm": "^8.0.4", + "@storybook/blocks": "^8.0.4", + "@storybook/manager-api": "^8.0.4", + "@storybook/react": "^8.0.4", + "@storybook/react-vite": "^8.0.4", + "@storybook/theming": "^8.0.4", + "@types/react": "^18", + "@types/react-dom": "^18", + "eslint-plugin-storybook": "^0.8.0", + "jest-axe": "^8.0.0", + "release-it": "17.1.1", + "storybook": "^8.0.4", + "tsconfig": "workspace:*", + "typescript": "^5.6.3", + "vite": "^5.4.8", + "vitest": "^2.1.3", + "vitest-axe": "^0.1.0" + } +} diff --git a/packages/cmsui/setupTesting.ts b/packages/cmsui/setupTesting.ts new file mode 100644 index 0000000000..8bc87fa36e --- /dev/null +++ b/packages/cmsui/setupTesting.ts @@ -0,0 +1,3 @@ +import '@testing-library/jest-dom'; +import { toHaveNoViolations } from 'jest-axe'; +expect.extend(toHaveNoViolations); diff --git a/packages/cmsui/stories.ts b/packages/cmsui/stories.ts new file mode 100644 index 0000000000..6a668df163 --- /dev/null +++ b/packages/cmsui/stories.ts @@ -0,0 +1,22 @@ +export const storyData = { + blocks: { + '7ab29abe-b38c-406b-94d7-b270e544a998': { + '@type': 'slate', + value: [ + { + type: 'p', + children: [ + { + text: 'Lorem ipsum dolor sit amet eu tempus ornare elit. Curabitur egestas quisque molestie pellentesque nunc imperdiet posuere morbi nunc eleifend. Volutpat enim augue blandit aliquam interdum pulvinar eu mattis congue. Eleifend mauris ut fermentum egestas mi faucibus adipiscing arcu nibh scelerisque justo habitasse. Mi consectetur hac maecenas leo dictumst vitae phasellus quam praesent vivamus nullam imperdiet integer mauris.', + }, + ], + }, + ], + plaintext: + 'Lorem ipsum dolor sit amet eu tempus ornare elit. Curabitur egestas quisque molestie pellentesque nunc imperdiet posuere morbi nunc eleifend. Volutpat enim augue blandit aliquam interdum pulvinar eu mattis congue. Eleifend mauris ut fermentum egestas mi faucibus adipiscing arcu nibh scelerisque justo habitasse. Mi consectetur hac maecenas leo dictumst vitae phasellus quam praesent vivamus nullam imperdiet integer mauris.', + }, + }, + blocks_layout: { + items: ['7ab29abe-b38c-406b-94d7-b270e544a998'], + }, +}; diff --git a/packages/cmsui/towncrier.toml b/packages/cmsui/towncrier.toml new file mode 100644 index 0000000000..3ef721f378 --- /dev/null +++ b/packages/cmsui/towncrier.toml @@ -0,0 +1,33 @@ +[tool.towncrier] +filename = "CHANGELOG.md" +directory = "news/" +title_format = "## {version} ({project_date})" +underlines = ["", "", ""] +template = "../scripts/templates/towncrier_template.jinja" +start_string = "\n" +issue_format = "[#{issue}](https://github.com/plone/volto/issues/{issue})" + +[[tool.towncrier.type]] +directory = "breaking" +name = "Breaking" +showcontent = true + +[[tool.towncrier.type]] +directory = "feature" +name = "Feature" +showcontent = true + +[[tool.towncrier.type]] +directory = "bugfix" +name = "Bugfix" +showcontent = true + +[[tool.towncrier.type]] +directory = "internal" +name = "Internal" +showcontent = true + +[[tool.towncrier.type]] +directory = "documentation" +name = "Documentation" +showcontent = true diff --git a/packages/cmsui/tsconfig.json b/packages/cmsui/tsconfig.json new file mode 100644 index 0000000000..2965402840 --- /dev/null +++ b/packages/cmsui/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "tsconfig/react-library.json", + "include": ["**/*.ts", "**/*.tsx"], + "exclude": [ + "node_modules", + "build", + "public", + "coverage", + "src/**/*.test.{js,jsx,ts,tsx}", + "src/**/*.spec.{js,jsx,ts,tsx}", + "src/**/*.stories.{js,jsx,ts,tsx}" + ] +} diff --git a/packages/cmsui/vitest.config.ts b/packages/cmsui/vitest.config.ts new file mode 100644 index 0000000000..fddf5f61f7 --- /dev/null +++ b/packages/cmsui/vitest.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vitest/config'; + +// https://vitejs.dev/config/ +export default defineConfig({ + test: { + globals: true, + environment: 'jsdom', + setupFiles: './setupTesting.ts', + // you might want to disable it, if you don't have tests that rely on CSS + // since parsing CSS is slow + css: true, + exclude: ['**/node_modules/**', '**/lib/**'], + }, +}); diff --git a/packages/volto/.gitignore b/packages/volto/.gitignore index 6d1cc045b4..fdb83bb779 100644 --- a/packages/volto/.gitignore +++ b/packages/volto/.gitignore @@ -10,6 +10,7 @@ eslint.xml yarn-error.log build .changelog.draft +.registry.loader.js # yarn 3 .pnp.* diff --git a/packages/volto/package.json b/packages/volto/package.json index 64d9b1c45b..6ad031b51e 100644 --- a/packages/volto/package.json +++ b/packages/volto/package.json @@ -30,6 +30,9 @@ "coreAddons": { "volto-slate": { "package": "@plone/volto-slate" + }, + "cmsui": { + "package": "@plone/cmsui" } }, "main": "src/index.js", @@ -179,6 +182,7 @@ "dependencies": { "@loadable/component": "5.14.1", "@loadable/server": "5.14.0", + "@plone/cmsui": "workspace:*", "@plone/registry": "workspace:*", "@plone/scripts": "workspace:*", "@plone/volto-slate": "workspace:*", diff --git a/packages/volto/src/components/manage/Form/Form.jsx b/packages/volto/src/components/manage/Form/Form.jsx index d19b68a603..e5b358b3e0 100644 --- a/packages/volto/src/components/manage/Form/Form.jsx +++ b/packages/volto/src/components/manage/Form/Form.jsx @@ -5,7 +5,8 @@ import Icon from '@plone/volto/components/theme/Icon/Icon'; import Toast from '@plone/volto/components/manage/Toast/Toast'; -import { Field, BlocksForm } from '@plone/volto/components/manage/Form'; +import { BlocksForm } from '@plone/volto/components/manage/Form'; +import Field from '@plone/cmsui/components/Field/Field'; import BlocksToolbar from '@plone/volto/components/manage/Form/BlocksToolbar'; import UndoToolbar from '@plone/volto/components/manage/Form/UndoToolbar'; import { difference } from '@plone/volto/helpers/Utils/Utils'; diff --git a/packages/volto/src/components/manage/Form/InlineForm.jsx b/packages/volto/src/components/manage/Form/InlineForm.jsx index a92f6aaba2..6e132a9f74 100644 --- a/packages/volto/src/components/manage/Form/InlineForm.jsx +++ b/packages/volto/src/components/manage/Form/InlineForm.jsx @@ -14,7 +14,7 @@ import { arrayRange, } from '@plone/volto/helpers/Utils/Utils'; import Icon from '@plone/volto/components/theme/Icon/Icon'; -import { Field } from '@plone/volto/components/manage/Form'; +import Field from '@plone/cmsui/components/Field/Field'; import { applySchemaDefaults } from '@plone/volto/helpers/Blocks/Blocks'; import upSVG from '@plone/volto/icons/up-key.svg'; diff --git a/packages/volto/src/config/index.js b/packages/volto/src/config/index.js index 48ab6e0159..f4a497b546 100644 --- a/packages/volto/src/config/index.js +++ b/packages/volto/src/config/index.js @@ -177,6 +177,14 @@ let config = { includeSiteTitle: false, titleAndSiteTitleSeparator: '-', }, + cssLayers: [ + 'reset', + 'semanticUI', + 'plone-components', + 'layout', + 'addons', + 'theme', + ], }, experimental: { addBlockButton: { diff --git a/packages/volto/src/loadSemanticUI.less b/packages/volto/src/loadSemanticUI.less new file mode 100644 index 0000000000..be2ef838ac --- /dev/null +++ b/packages/volto/src/loadSemanticUI.less @@ -0,0 +1,4 @@ +@layer semanticUI { + @import 'semantic-ui-less/semantic.less'; + @import '@plone/volto/../theme/themes/pastanaga/extras/extras.less'; +} diff --git a/packages/volto/src/styles/main.css b/packages/volto/src/styles/main.css new file mode 100644 index 0000000000..eb3c025cb8 --- /dev/null +++ b/packages/volto/src/styles/main.css @@ -0,0 +1,2 @@ +@import 'variables.css'; +@import 'sidebar.css'; diff --git a/packages/volto/src/styles/sidebar.css b/packages/volto/src/styles/sidebar.css new file mode 100644 index 0000000000..588b700570 --- /dev/null +++ b/packages/volto/src/styles/sidebar.css @@ -0,0 +1,19 @@ +/* Todo: Better class naming to identify things in the sidebar? */ +.sidebar-container { + @layer plone-components { + /* ToDo: Infer a `field` classname? */ + .q.react-aria-TextField { + width: 100%; + } + + .q.react-aria-TextField, + .q.react-aria-TextArea { + margin-bottom: var(--form-spacing-small); + } + + .q.react-aria-TextField:first-child, + .q.react-aria-TextArea:first-child { + margin-top: var(--form-spacing-small); + } + } +} diff --git a/packages/volto/src/styles/variables.css b/packages/volto/src/styles/variables.css new file mode 100644 index 0000000000..b7cf02e909 --- /dev/null +++ b/packages/volto/src/styles/variables.css @@ -0,0 +1,3 @@ +:root { + --form-spacing-small: 25px; +} diff --git a/packages/volto/src/theme.js b/packages/volto/src/theme.js index 44b93a05b8..b14b184861 100644 --- a/packages/volto/src/theme.js +++ b/packages/volto/src/theme.js @@ -1,12 +1,3 @@ -/** - * If you want to apply semantic ui only to the toolbar - * and all administrative or editorial views, - * uncomment the following line and comment the line: - * `import 'semantic-ui-less/semantic.less';` - * - * Then, in your `theme.config` file, change the following variable: - * `@container : 'pastanaga-cms-ui'` - */ -// import '../theme/themes/pastanaga-cms-ui/extras/cms-ui.semantic.less'; -import 'semantic-ui-less/semantic.less'; -import '../theme/themes/pastanaga/extras/extras.less'; +// Load SemanticUI behind a layer +import './loadSemanticUI.less'; +import './styles/main.css'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae6dfec91f..72835d24c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -517,6 +517,91 @@ importers: specifier: ^7.2.0 version: 7.2.0(debug@4.3.4) + packages/cmsui: + dependencies: + '@plone/client': + specifier: workspace:* + version: link:../client + '@plone/components': + specifier: workspace:* + version: link:../components + '@plone/registry': + specifier: workspace:* + version: link:../registry + react: + specifier: ^16.8.0 || ^17.0.0 || ^18.0.0 + version: 18.2.0 + react-aria-components: + specifier: ^1.5.0 + version: 1.5.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react-dom: + specifier: ^16.8.0 || ^17.0.0 || ^18.0.0 + version: 18.2.0(react@18.2.0) + devDependencies: + '@plone/types': + specifier: workspace:* + version: link:../types + '@storybook/addon-essentials': + specifier: ^8.0.4 + version: 8.0.8(@types/react@18.3.12)(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/addon-interactions': + specifier: ^8.0.4 + version: 8.0.8(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.12.7))(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.28.1)(sass@1.75.0)(terser@5.30.3)) + '@storybook/addon-links': + specifier: ^8.0.4 + version: 8.0.8(react@18.2.0) + '@storybook/addon-mdx-gfm': + specifier: ^8.0.4 + version: 8.0.8 + '@storybook/blocks': + specifier: ^8.0.4 + version: 8.0.8(@types/react@18.3.12)(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/manager-api': + specifier: ^8.0.4 + version: 8.0.8(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/react': + specifier: ^8.0.4 + version: 8.0.8(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.6.3) + '@storybook/react-vite': + specifier: ^8.0.4 + version: 8.0.8(@preact/preset-vite@2.8.2(@babel/core@7.25.8)(vite@5.4.9(@types/node@20.12.7)(less@3.11.1)(lightningcss@1.28.1)(sass@1.75.0)(terser@5.30.3)))(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(rollup@4.24.0)(typescript@5.6.3)(vite@5.4.9(@types/node@20.12.7)(less@3.11.1)(lightningcss@1.28.1)(sass@1.75.0)(terser@5.30.3)) + '@storybook/theming': + specifier: ^8.0.4 + version: 8.0.8(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@types/react': + specifier: ^18 + version: 18.3.12 + '@types/react-dom': + specifier: ^18 + version: 18.3.1 + eslint-plugin-storybook: + specifier: ^0.8.0 + version: 0.8.0(eslint@8.57.0)(typescript@5.6.3) + jest-axe: + specifier: ^8.0.0 + version: 8.0.0 + release-it: + specifier: 17.1.1 + version: 17.1.1(typescript@5.6.3) + storybook: + specifier: ^8.0.4 + version: 8.0.8(@babel/preset-env@7.24.4(@babel/core@7.25.8))(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + tsconfig: + specifier: workspace:* + version: link:../tsconfig + typescript: + specifier: ^5.6.3 + version: 5.6.3 + vite: + specifier: ^5.4.8 + version: 5.4.9(@types/node@20.12.7)(less@3.11.1)(lightningcss@1.28.1)(sass@1.75.0)(terser@5.30.3) + vitest: + specifier: ^2.1.3 + version: 2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.28.1)(sass@1.75.0)(terser@5.30.3) + vitest-axe: + specifier: ^0.1.0 + version: 0.1.0(vitest@2.1.3(@types/node@20.12.7)(jsdom@22.1.0)(less@3.11.1)(lightningcss@1.28.1)(sass@1.75.0)(terser@5.30.3)) + packages/components: dependencies: '@react-aria/utils': @@ -1157,6 +1242,9 @@ importers: '@loadable/server': specifier: 5.14.0 version: 5.14.0(@loadable/component@5.14.1(react@18.2.0))(react@18.2.0) + '@plone/cmsui': + specifier: workspace:* + version: link:../cmsui '@plone/registry': specifier: workspace:* version: link:../registry @@ -18340,7 +18428,7 @@ snapshots: dependencies: '@babel/core': 7.25.8 '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.25.8) - '@babel/helper-plugin-utils': 7.24.0 + '@babel/helper-plugin-utils': 7.25.7 '@babel/plugin-transform-arrow-functions@7.24.1(@babel/core@7.24.4)': dependencies: @@ -18378,9 +18466,11 @@ snapshots: '@babel/plugin-transform-async-to-generator@7.24.1(@babel/core@7.25.8)': dependencies: '@babel/core': 7.25.8 - '@babel/helper-module-imports': 7.24.3 + '@babel/helper-module-imports': 7.25.7 '@babel/helper-plugin-utils': 7.25.7 '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.25.8) + transitivePeerDependencies: + - supports-color '@babel/plugin-transform-block-scoped-functions@7.24.1(@babel/core@7.24.4)': dependencies: @@ -23526,12 +23616,12 @@ snapshots: '@storybook/react-vite@8.0.8(@preact/preset-vite@2.8.2(@babel/core@7.25.8)(vite@5.4.8(@types/node@20.12.7)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3)))(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(rollup@4.24.0)(typescript@5.6.3)(vite@5.4.8(@types/node@20.12.7)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3))': dependencies: '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.6.3)(vite@5.4.8(@types/node@20.12.7)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3)) - '@rollup/pluginutils': 5.1.0(rollup@4.24.0) + '@rollup/pluginutils': 5.1.2(rollup@4.24.0) '@storybook/builder-vite': 8.0.8(@preact/preset-vite@2.8.2(@babel/core@7.25.8)(vite@5.4.8(@types/node@20.12.7)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3)))(encoding@0.1.13)(typescript@5.6.3)(vite@5.4.8(@types/node@20.12.7)(less@3.11.1)(lightningcss@1.24.1)(sass@1.75.0)(terser@5.30.3)) '@storybook/node-logger': 8.0.8 '@storybook/react': 8.0.8(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.6.3) find-up: 5.0.0 - magic-string: 0.30.10 + magic-string: 0.30.11 react: 18.2.0 react-docgen: 7.0.3 react-dom: 18.2.0(react@18.2.0)