diff --git a/docs/ADAPTIVITY_GUIDE.md b/docs/ADAPTIVITY_GUIDE.md index 486e791b21..fa9fd405f2 100644 --- a/docs/ADAPTIVITY_GUIDE.md +++ b/docs/ADAPTIVITY_GUIDE.md @@ -32,8 +32,8 @@ import { useAdaptivity } from '../../hooks/useAdaptivity'; import styles from './Component.module.css'; const sizeXClassNames = { - none: styles['Component--sizeX-none'], // означает, что sizeX не определён в AdaptivityProvider – используем `@media` - compact: styles['Component--sizeX-compact'], + none: styles.hostSizeXNone, // означает, что sizeX не определён в AdaptivityProvider – используем `@media` + compact: styles.hostSizeXCompact, }; const Component = () => { @@ -42,7 +42,7 @@ const Component = () => { return (
{ _Component.module.css_ ```css -/* Равносильно модификатору `Component--sizeX-regular` */ -.Component { +/* Равносильно модификатору `sizeXRegular` */ +.host { color: red; padding: 20px; } -.Component--sizeX-compact { +.sizeXCompact { padding: 10px; } @media (--sizeX-compact) { - .Component--sizeX-none { + .sizeXNone { padding: 10px; } } @@ -91,9 +91,9 @@ import { ViewWidth, viewWidthToClassName } from '../../../lib/adaptivity'; import styles from './Component.module.css'; const viewWidthClassNames = { - none: styles['Component--viewWidth-none'], // означает, что viewWidth не определён в AdaptivityProvider – используем `@media` - smallTabletMinus: styles['Component--viewWidth-smallTabletMinus'], - smallTabletPlus: styles['Component--viewWidth-smallTabletPlus'], + none: styles.viewWidthNone, // означает, что viewWidth не определён в AdaptivityProvider – используем `@media` + smallTabletMinus: styles.viewWidthSmallTabletMinus, + smallTabletPlus: styles.viewWidthSmallTabletPlus, }; const Component = () => { @@ -110,26 +110,26 @@ const Component = () => { _Component.module.css_ ```css -.Component { +.host { color: red; } -.Component--viewWidth-smallTabletPlus { +.viewWidthSmallTabletPlus { color: blue; } @media (--viewWidth-smallTabletPlus) { - .Component--viewWidth-none { + .viewWidthNone { color: blue; } } -.Component--viewWidth-smallTabletMinus { +.viewWidthSmallTabletMinus { color: green; } @media (--viewWidth-smallTabletMinus) { - .Component--viewWidth-none { + .viewWidthNone { color: green; } } @@ -148,23 +148,21 @@ _Component.module.css_ В процессе перевода существующих компонентов на новую систему адаптивности возникли места, в которых пришлось отступить от стандартного поведения. -- `.Group--mode-none` +- `.modeNone` - В компоненте [Group](../packages/vkui/src/components/Group/Group.tsx) появился класс `.Group--mode-none`. Он означает, что у `Group` - не передан `mode` и не удалось вычислить его автоматически. `.Group--mode-none` должен вести себя как - `.Group--mode-card` при `sizeX=regular` и как `.Group--mode-plain` при `sizeX=compact`. Пример использования: + В компоненте [Group](../packages/vkui/src/components/Group/Group.tsx) появился класс `.modeNone`. Он означает, что у `Group` + не передан `mode` и не удалось вычислить его автоматически. `.modeNone` должен вести себя как + `.modeCard` при `sizeX=regular` и как `.modePlain` при `sizeX=compact`. Пример использования: ```css - .Group--mode-card .CardGrid { - padding-left: 8px; - padding-right: 8px; + .modeCard .сomponent { + padding-inline: 8px; } @media (--sizeX-regular) { - /* Применяем стили `.Group--mode-card`, если не задан `mode` и `sizeX=regular` */ - .Group--mode-none .CardGrid { - padding-left: 8px; - padding-right: 8px; + /* Применяем стили `.modeCard`, если не задан `mode` и `sizeX=regular` */ + .modeNone .in { + padding-inline: 8px; } } ``` diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 4a9828891b..e540f59894 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -42,7 +42,7 @@ git config blame.ignoreRevsFile .git-blame-ignore-revs > Не используем композицию, т.к. в ней нет необходимости, > а также в будущем она может усложнить переход на другое решение. -- CSS-классы названы по БЭМ: `.Component__element-name--modificator`. [Гайд по написанию стилей](https://github.com/VKCOM/VKUI/blob/master/docs/CSS_GUIDE.md) +- CSS-классы должны быть в формате camelCase: `elementNameModification`. [Гайд по написанию стилей](https://github.com/VKCOM/VKUI/blob/master/docs/CSS_GUIDE.md) - Свойства `className` и `style` навешиваются на корневой элемент компонента - Свойства, не используемые в коде компонента, навешиваются на **главный** элемент компонента. По умолчанию главным является корневой элемент: @@ -58,11 +58,7 @@ git config blame.ignoreRevsFile .git-blame-ignore-revs const Input = ({ mode, style, className, ...restProps }) => { return (
@@ -78,11 +74,7 @@ git config blame.ignoreRevsFile .git-blame-ignore-revs const Component = ({ mode = 'default', className, ...restProps }) => (
); diff --git a/docs/CSS_GUIDE.md b/docs/CSS_GUIDE.md index 8c58fcdff4..26c0959d1f 100644 --- a/docs/CSS_GUIDE.md +++ b/docs/CSS_GUIDE.md @@ -2,13 +2,27 @@ ## Соглашения -- Используем БЭМ-нотацию -- Блок начинается с заглавной буквы: `.Checkbox` -- Многословный блок разделяется через camelCase: `.ButtonGroup` -- Элемент от блока отделяется двумя подчеркиваниями: `.Checkbox__in` -- Многословные элементы разделяются через kebab-case: `.Banner__before-title` -- Модификатор отделяется двумя дефисами: `.Input--plain` (см. **Работа с модификаторами**) -- Многословные модификаторы разделяются через kebab-case или camelCase: `.Checkbox--sizeX-regular` +### Используйте camelCase для имён классов + +Для всех `.module.css` файлов активирован линтер, который проверяет, чтобы локальные классы были в camelCase. Именно эти классы затем будут импортироваться в JS и именно они затем будут отображаться в инспекторе браузера, если вы решите посмотреть какой-то элемент. Такой формат выбран в том числе для удобства написания и отладки кода. + +### Модификаторы + +У модификаторов элемента должен быть префикс с названием этого элемента. Например, + +```css +.container { +} +.containerPrimary { +} +.containerSecondary { +} + +.text { +} +.textWithShadow { +} +``` ### Работа с модификаторами @@ -21,17 +35,17 @@ import { useCSSKeyframesAnimationController } from '../../lib/animation'; import styles from './Component.module.css'; const animationStateClassNames = { - enter: styles['Component--enter'], - entering: styles['Component--enter'], - entered: styles['Component--entered'], - exit: styles['Component--exit'], - exiting: styles['Component--exit'], - exited: styles['Component--exited'], + enter: styles.hostEnter, + entering: styles.hostEnter, + entered: styles.hostEntered, + exit: styles.hostExit, + exiting: styles.hostExit, + exited: styles.hostExited, }; const platformClassNames = { - android: styles['Component--android'], - vkcom: styles['Component--vkcom'], + android: styles.hostAndroid, + vkcom: styles.hostVKCOM, }; const Component = ({ className, children }) => { @@ -41,7 +55,7 @@ const Component = ({ className, children }) => {
{ return (
{children} @@ -73,31 +87,13 @@ const Component = ({ objectFit, children }) => { ``` ```css -.Component { +.host { --vkui_internal_Component_object-fit: initial; object-fit: var(--vkui_internal_Component_object-fit); } ``` -## Связность стилей - -Если компонент состоит из других компонентов, то для их модификации используем БЭМ-миксин. Пример: - -```tsx -// Button.tsx - -``` - -```css -/* Button.module.css */ -.Button__text { - padding: 8px; -} -``` - ## Глобальные классы Класс, который начинается с `vkui` и `vkuiInternal`, обозначает, что он глобальный. Например, @@ -110,7 +106,7 @@ const Component = ({ objectFit, children }) => { ```jsx // Cell.tsx -
{before}
+
{before}
``` В `before` может быть `` или иконка из библиотеки `@vkontakte/icons`. И нам необходимо @@ -121,7 +117,7 @@ const Component = ({ objectFit, children }) => { ```css /* Avatar.module.css */ -:global(.vkuiInternalCell) .Avatar { +:global(.vkuiInternalCell) .host { margin-inline-end: 8px; } ``` @@ -131,7 +127,7 @@ const Component = ({ objectFit, children }) => { ```css /* Cell.module.css */ -.Cell :global(.vkuiIcon) { +.host :global(.vkuiIcon) { margin-inline-end: 10px; } ``` @@ -142,14 +138,14 @@ const Component = ({ objectFit, children }) => { ```css /* Text.module.css */ -.Text { +.host { margin: 0; } ``` ```css /* Button.module.css */ -.Button__text { +.text { margin: 4px 0; } ``` @@ -175,7 +171,7 @@ const Component = ({ objectFit, children }) => { ```css /* Placeholder.module.css */ -.Placeholder { +.host { padding: 16px; } ``` @@ -195,8 +191,7 @@ Button мы тоже наделяем возможностью рендерит ```css /* Button.module.css */ - -.Button { +.host { /* ... */ display: inline-block; } @@ -213,8 +208,8 @@ Button мы тоже наделяем возможностью рендерит ### Обращения к элементам другого блока ```css -/* Banner.css */ -.Banner .Button__in { +/* Banner.module.css */ +.host .vkuiButton__in { padding-top: 4px; } ``` diff --git a/packages/codemods/src/codemod-helpers.ts b/packages/codemods/src/codemod-helpers.ts index 43f517797e..df831acc86 100644 --- a/packages/codemods/src/codemod-helpers.ts +++ b/packages/codemods/src/codemod-helpers.ts @@ -1,3 +1,4 @@ +import { JSXSpreadAttribute } from 'jscodeshift'; import type { API, Collection, @@ -91,6 +92,13 @@ export function swapBooleanValue( }); } +export const removeAttribute = ( + attributes: Array | undefined, + attribute: JSXAttribute, +) => { + attributes?.splice(attributes?.indexOf(attribute), 1); +}; + interface AttributeManipulatorAPI { keyTo?: string | ((k?: string) => string); reportText?: string | (() => string); diff --git a/packages/codemods/src/transforms/v7/__testfixtures__/image-overlay/image-base.input.tsx b/packages/codemods/src/transforms/v7/__testfixtures__/image-overlay/image-base.input.tsx new file mode 100644 index 0000000000..a7e1c43faf --- /dev/null +++ b/packages/codemods/src/transforms/v7/__testfixtures__/image-overlay/image-base.input.tsx @@ -0,0 +1,66 @@ +import { ImageBase } from '@vkontakte/vkui'; +import { Icon12Add } from '@vkontakte/vkui-icons'; +import React from 'react'; + +const App = () => { + const callback = () => {}; + return ( + + {/* test 1: disableInteractive={true} -> remove disableInteractive and onClick */} + + + + + + + {/* test 2: disableInteractive -> remove disableInteractive and onClick */} + + + + + + + {/* test 3: disableInteractive={false} and onClick with Identifier -> remove disableInteractive, don't remove onClick */} + + + + + + + {/* test 4: disableInteractive={false} and onClick with ArrowFunction -> remove disableInteractive, don't remove onClick */} + + callback()} + > + + + + + ); +}; diff --git a/packages/codemods/src/transforms/v7/__testfixtures__/image-overlay/image.input.tsx b/packages/codemods/src/transforms/v7/__testfixtures__/image-overlay/image.input.tsx new file mode 100644 index 0000000000..eb5a01a6e8 --- /dev/null +++ b/packages/codemods/src/transforms/v7/__testfixtures__/image-overlay/image.input.tsx @@ -0,0 +1,66 @@ +import { Image } from '@vkontakte/vkui'; +import { Icon12Add } from '@vkontakte/vkui-icons'; +import React from 'react'; + +const App = () => { + const callback = () => {}; + return ( + + {/* test 1: disableInteractive={true} -> remove disableInteractive and onClick */} + Приложение шторм онлайн + + + + + + {/* test 2: disableInteractive -> remove disableInteractive and onClick */} + Приложение шторм онлайн + + + + + + {/* test 3: disableInteractive={false} and onClick with Identifier -> remove disableInteractive, don't remove onClick */} + Приложение шторм онлайн + + + + + + {/* test 4: disableInteractive={false} and onClick with ArrowFunction -> remove disableInteractive, don't remove onClick */} + Приложение шторм онлайн + callback()} + > + + + + + ); +}; diff --git a/packages/codemods/src/transforms/v7/__testfixtures__/users-stack/basic.input.tsx b/packages/codemods/src/transforms/v7/__testfixtures__/users-stack/basic.input.tsx new file mode 100644 index 0000000000..a24b9c42f2 --- /dev/null +++ b/packages/codemods/src/transforms/v7/__testfixtures__/users-stack/basic.input.tsx @@ -0,0 +1,43 @@ +import { UsersStack } from '@vkontakte/vkui'; +import React from 'react'; + +const App = () => { + return ( + + {/* direction="row" -> avatarsPosition="start" */} + + Иван и ещё 2 ваших друга подписаны + + + {/* direction="row-reverse" -> avatarsPosition="end" */} + + Иван и ещё 2 ваших друга подписаны + + + {/* direction="column" -> avatarsPosition="top" */} + + Иван и ещё 2 ваших друга подписаны + + + {/* do nothing */} + + Иван и ещё 2 ваших друга подписаны + + + ); +}; diff --git a/packages/codemods/src/transforms/v7/__tests__/__snapshots__/image-overlay.ts.snap b/packages/codemods/src/transforms/v7/__tests__/__snapshots__/image-overlay.ts.snap new file mode 100644 index 0000000000..8e54830375 --- /dev/null +++ b/packages/codemods/src/transforms/v7/__tests__/__snapshots__/image-overlay.ts.snap @@ -0,0 +1,101 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`image-overlay transforms correctly 1`] = ` +"import { Image } from '@vkontakte/vkui'; +import { Icon12Add } from '@vkontakte/vkui-icons'; +import React from 'react'; + +const App = () => { + const callback = () => {}; + return ( + ( + {/* test 1: disableInteractive={true} -> remove disableInteractive and onClick */} + Приложение шторм онлайн + + + + + {/* test 2: disableInteractive -> remove disableInteractive and onClick */} + Приложение шторм онлайн + + + + + {/* test 3: disableInteractive={false} and onClick with Identifier -> remove disableInteractive, don't remove onClick */} + Приложение шторм онлайн + + + + + {/* test 4: disableInteractive={false} and onClick with ArrowFunction -> remove disableInteractive, don't remove onClick */} + Приложение шторм онлайн + callback()}> + + + + ) + ); +};" +`; + +exports[`image-overlay transforms correctly 2`] = ` +"import { ImageBase } from '@vkontakte/vkui'; +import { Icon12Add } from '@vkontakte/vkui-icons'; +import React from 'react'; + +const App = () => { + const callback = () => {}; + return ( + ( + {/* test 1: disableInteractive={true} -> remove disableInteractive and onClick */} + + + + + + {/* test 2: disableInteractive -> remove disableInteractive and onClick */} + + + + + + {/* test 3: disableInteractive={false} and onClick with Identifier -> remove disableInteractive, don't remove onClick */} + + + + + + {/* test 4: disableInteractive={false} and onClick with ArrowFunction -> remove disableInteractive, don't remove onClick */} + + callback()}> + + + + ) + ); +};" +`; diff --git a/packages/codemods/src/transforms/v7/__tests__/__snapshots__/users-stack.ts.snap b/packages/codemods/src/transforms/v7/__tests__/__snapshots__/users-stack.ts.snap new file mode 100644 index 0000000000..159d6ee98f --- /dev/null +++ b/packages/codemods/src/transforms/v7/__tests__/__snapshots__/users-stack.ts.snap @@ -0,0 +1,44 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`users-stack transforms correctly 1`] = ` +"import { UsersStack } from '@vkontakte/vkui'; +import React from 'react'; + +const App = () => { + return ( + ( + {/* direction="row" -> avatarsPosition="start" */} + + Иван и ещё 2 ваших друга подписаны + + {/* direction="row-reverse" -> avatarsPosition="end" */} + + Иван и ещё 2 ваших друга подписаны + + {/* direction="column" -> avatarsPosition="top" */} + + Иван и ещё 2 ваших друга подписаны + + {/* do nothing */} + + Иван и ещё 2 ваших друга подписаны + + ) + ); +};" +`; diff --git a/packages/codemods/src/transforms/v7/__tests__/image-overlay.ts b/packages/codemods/src/transforms/v7/__tests__/image-overlay.ts new file mode 100644 index 0000000000..a88a304705 --- /dev/null +++ b/packages/codemods/src/transforms/v7/__tests__/image-overlay.ts @@ -0,0 +1,11 @@ +jest.autoMockOff(); +import { defineSnapshotTestFromFixture } from '../../../testHelpers/testHelper'; + +const name = 'image-overlay'; +const fixtures = ['image', 'image-base'] as const; + +describe(name, () => { + fixtures.forEach((test) => + defineSnapshotTestFromFixture(__dirname, name, global.TRANSFORM_OPTIONS, `${name}/${test}`), + ); +}); diff --git a/packages/codemods/src/transforms/v7/__tests__/users-stack.ts b/packages/codemods/src/transforms/v7/__tests__/users-stack.ts new file mode 100644 index 0000000000..fce4c5a407 --- /dev/null +++ b/packages/codemods/src/transforms/v7/__tests__/users-stack.ts @@ -0,0 +1,11 @@ +jest.autoMockOff(); +import { defineSnapshotTestFromFixture } from '../../../testHelpers/testHelper'; + +const name = 'users-stack'; +const fixtures = ['basic'] as const; + +describe(name, () => { + fixtures.forEach((test) => + defineSnapshotTestFromFixture(__dirname, name, global.TRANSFORM_OPTIONS, `${name}/${test}`), + ); +}); diff --git a/packages/codemods/src/transforms/v7/image-overlay.ts b/packages/codemods/src/transforms/v7/image-overlay.ts new file mode 100644 index 0000000000..ec98fef52f --- /dev/null +++ b/packages/codemods/src/transforms/v7/image-overlay.ts @@ -0,0 +1,156 @@ +import chalk from 'chalk'; +import { API, FileInfo, JSXAttribute, JSXSpreadAttribute } from 'jscodeshift'; +import { getImportInfo, removeAttribute } from '../../codemod-helpers'; +import { report } from '../../report'; +import { JSCodeShiftOptions } from '../../types'; + +export const parser = 'tsx'; + +export default function transformer(file: FileInfo, api: API, options: JSCodeShiftOptions) { + const { alias } = options; + const j = api.jscodeshift; + const source = j(file.source); + const { localName: ImageLocalName } = getImportInfo(j, file, 'Image', alias); + const { localName: ImageBaseLocalName } = getImportInfo(j, file, 'ImageBase', alias); + + if (!ImageLocalName && !ImageBaseLocalName) { + return source.toSource(); + } + + const findAttribute = ( + attributes: Array | undefined, + attributeName: string, + ): JSXAttribute | undefined => { + return ( + (attributes?.find((attr) => { + return attr.type === 'JSXAttribute' && attr.name.name === attributeName; + }) as JSXAttribute) || null + ); + }; + + const showReport = (localName: string, additionalMessage: string) => { + report( + api, + `: ${chalk.white.bgBlue(`${localName}.Overlay`)} has been changed. Manual changes required: ${additionalMessage}`, + ); + }; + + const showDisableInteractivePropReport = (localName: string) => { + showReport( + localName, + `"disableInteractive" has been removed, please use "onClick" if you want to make ${localName}.Overlay interactive`, + ); + }; + + const calcDisableInteractiveValue = ( + disableInteractiveAttribute: JSXAttribute, + ): boolean | null => { + if (disableInteractiveAttribute.value?.type === 'BooleanLiteral') { + return disableInteractiveAttribute.value.value; + } else if (disableInteractiveAttribute.value === null) { + return true; + } else if (disableInteractiveAttribute.value?.type === 'JSXExpressionContainer') { + const expression = disableInteractiveAttribute.value.expression; + if (expression.type === 'BooleanLiteral') { + return expression.value; + } + if (expression.type === 'Identifier' && expression.name === 'undefined') { + return false; + } + } + return null; + }; + + const handleImageComponent = (localName: string) => { + source + .find(j.JSXElement, { + openingElement: { + name: { + type: 'JSXMemberExpression', + object: { name: localName }, + property: { name: 'Overlay' }, + }, + }, + }) + .forEach((path) => { + const overlay = path.node; + const overlayItemAttributes = overlay.openingElement.attributes; + + const onClickAttribute: JSXAttribute | undefined = findAttribute( + overlayItemAttributes, + 'onClick', + ); + const disableInteractiveAttribute: JSXAttribute | undefined = findAttribute( + overlayItemAttributes, + 'disableInteractive', + ); + + // Кейс, когда disableInteractive не был задан, значит overlay interactive. + // Сейчас у него обязательно должен быть onClick, чтобы не ломать обратную совместимость + + if (!disableInteractiveAttribute) { + // Проверяем наличие onClick, и если его нет, то пользователь должен добавить onClick + if (!onClickAttribute) { + showReport( + localName, + `If you want to make ${localName}.Overlay interactive please add "onClick" prop`, + ); + } + return; + } + // Рассчитываем значение disableInteractive в boolean + const disableInteractiveValue = calcDisableInteractiveValue(disableInteractiveAttribute); + if (disableInteractiveValue === null) { + // Если у disableInteractive используется сложное выражение + // То пользователь сам должен удалить этот проп, как ему нужно + showDisableInteractivePropReport(localName); + } + + // Удаляем аттрибут disableInteractive + removeAttribute(overlayItemAttributes, disableInteractiveAttribute); + + if (disableInteractiveValue) { + // Если disableInteractive = true, то все, что нам нужно это удалить атрибут onClick + // Важно: мы можем его спокойно удалить, так как в этом кейсе он может быть только undefined + if (onClickAttribute) { + removeAttribute(overlayItemAttributes, onClickAttribute); + } + return; + } + if (!onClickAttribute) { + // Если disableInteractive = false и onClick пропа нет, то пользователь должен его добавить + showDisableInteractivePropReport(localName); + return; + } + // Если disableInteractive = false и onClick не пустой надо обработать следующие кейс: + // onClick=undefined: надо добавить колбэк + // onClick=identifier: все хорошо,оставляем как есть + // В остальных случаях, надо чтобы пользователь убедился, что onClick устанавливается корректно + if (onClickAttribute.value?.type === 'JSXExpressionContainer') { + const expression = onClickAttribute.value.expression; + if (expression.type === 'Identifier') { + if (expression.name === 'undefined') { + showDisableInteractivePropReport(localName); + } + return; + } + if (expression.type === 'ArrowFunctionExpression') { + return; + } + } + showReport( + localName, + `"disableInteractive" has been removed, please validate that "onClick" prop value not falsy`, + ); + }); + }; + + if (ImageLocalName) { + handleImageComponent(ImageLocalName); + } + if (ImageBaseLocalName) { + handleImageComponent(ImageBaseLocalName); + } + + return source.toSource(); +} diff --git a/packages/codemods/src/transforms/v7/users-stack.ts b/packages/codemods/src/transforms/v7/users-stack.ts new file mode 100644 index 0000000000..e8dca21aef --- /dev/null +++ b/packages/codemods/src/transforms/v7/users-stack.ts @@ -0,0 +1,57 @@ +import { API, FileInfo, JSXAttribute } from 'jscodeshift'; +import { getImportInfo } from '../../codemod-helpers'; +import { report } from '../../report'; +import { JSCodeShiftOptions } from '../../types'; + +export const parser = 'tsx'; + +export default function transformer(file: FileInfo, api: API, options: JSCodeShiftOptions) { + const { alias } = options; + const j = api.jscodeshift; + const source = j(file.source); + const { localName } = getImportInfo(j, file, 'UsersStack', alias); + if (!localName) { + return source.toSource(); + } + + const attributeToReplace = 'direction'; + const newAttributeName = 'avatarsPosition'; + + const directionToAvatarsPosition: Record = { + 'row': 'inline-start', + 'row-reverse': 'inline-end', + 'column': 'block-start', + }; + + const getValueFromAttribute = (attribute: JSXAttribute): string | null => { + if (attribute.value?.type === 'StringLiteral') { + return attribute.value.value; + } + if (attribute.value?.type === 'JSXExpressionContainer') { + const expression = attribute.value.expression; + if (expression.type === 'StringLiteral') { + return expression.value; + } + } + return null; + }; + + source + .find(j.JSXElement, { openingElement: { name: { name: localName } } }) + .find(j.JSXAttribute, { name: { name: attributeToReplace } }) + .forEach((path) => { + const avatar = path.node; + avatar.name.name = newAttributeName; + const value = getValueFromAttribute(avatar); + if (!value || !directionToAvatarsPosition[value]) { + report( + api, + `: ${localName} has been changed. Manual changes required: need to change direction prop to avatarsPosition`, + ); + return; + } + avatar.value = j.stringLiteral(directionToAvatarsPosition[value]); + }); + + return source.toSource(); +} diff --git a/packages/vkui/package.swcrc b/packages/vkui/package.swcrc index d90c72a4d3..3a34698ab2 100644 --- a/packages/vkui/package.swcrc +++ b/packages/vkui/package.swcrc @@ -29,7 +29,7 @@ [ "swc-plugin-css-modules", { - "generate_scoped_name": "vkui[local]" + "generate_scoped_name": "vkui[folder]__[local]" } ], [ diff --git a/packages/vkui/src/components/Accordion/Accordion.module.css b/packages/vkui/src/components/Accordion/Accordion.module.css index c588597117..15bd3704e6 100644 --- a/packages/vkui/src/components/Accordion/Accordion.module.css +++ b/packages/vkui/src/components/Accordion/Accordion.module.css @@ -1,12 +1,12 @@ -.AccordionSummary__icon { +.icon { color: var(--vkui--color_icon_secondary); } -.AccordionContent { +.host { overflow: hidden; } -.AccordionContent__in { +.in { --vkui_internal--AccordionContent_height: initial; animation-duration: 100ms; @@ -15,37 +15,37 @@ } @media (--reduce-motion) { - .AccordionContent__in { + .in { animation-duration: 300ms; animation-timing-function: linear; } } -.AccordionContent__in--enter { +.inEnter { animation-name: animation-accordion-expand; } @media (--reduce-motion) { - .AccordionContent__in--enter { + .inEnter { animation-name: animation-accordion-fade-in; } } -.AccordionContent__in--entered { +.inEntered { block-size: var(--vkui_internal--AccordionContent_height); } -.AccordionContent__in--exit { +.inExit { animation-name: animation-accordion-collapse; } @media (--reduce-motion) { - .AccordionContent__in--exit { + .inExit { animation-name: animation-accordion-fade-out; } } -.AccordionContent__in--exited { +.inExited { block-size: 0; } diff --git a/packages/vkui/src/components/Accordion/AccordionContent.tsx b/packages/vkui/src/components/Accordion/AccordionContent.tsx index c3924943fe..b248c6fc2b 100644 --- a/packages/vkui/src/components/Accordion/AccordionContent.tsx +++ b/packages/vkui/src/components/Accordion/AccordionContent.tsx @@ -10,12 +10,12 @@ import styles from './Accordion.module.css'; const CUSTOM_PROPERTY_ACCORDION_CONTENT_HEIGHT = '--vkui_internal--AccordionContent_height'; const stateClassNames = { - enter: styles['AccordionContent__in--enter'], - entering: styles['AccordionContent__in--enter'], - entered: styles['AccordionContent__in--entered'], - exit: styles['AccordionContent__in--exit'], - exiting: styles['AccordionContent__in--exit'], - exited: styles['AccordionContent__in--exited'], + enter: styles.inEnter, + entering: styles.inEnter, + entered: styles.inEntered, + exit: styles.inExit, + exiting: styles.inExit, + exited: styles.inExited, }; export interface AccordionContentProps @@ -66,12 +66,12 @@ export const AccordionContent: React.FC = ({ role="region" aria-labelledby={labelId} aria-hidden={!expanded} - className={classNames(styles['AccordionContent'], className)} + className={classNames(styles.host, className)} {...restProps} >
{children} diff --git a/packages/vkui/src/components/Accordion/AccordionSummary.tsx b/packages/vkui/src/components/Accordion/AccordionSummary.tsx index a14a1c43df..9530aa99bc 100644 --- a/packages/vkui/src/components/Accordion/AccordionSummary.tsx +++ b/packages/vkui/src/components/Accordion/AccordionSummary.tsx @@ -37,7 +37,7 @@ export const AccordionSummary: React.FC = ({ const icon = ( // Обертка нужна для правильной работы с отступами в SimpleCell - + ); diff --git a/packages/vkui/src/components/ActionSheet/ActionSheet.module.css b/packages/vkui/src/components/ActionSheet/ActionSheet.module.css index 18062e6f6a..532afe65f4 100644 --- a/packages/vkui/src/components/ActionSheet/ActionSheet.module.css +++ b/packages/vkui/src/components/ActionSheet/ActionSheet.module.css @@ -1,4 +1,4 @@ -.ActionSheet { +.host { --vkui_internal--actionsheet_animation_opacity_initial: 1; --vkui_internal--actionsheet_animation_translateY_initial: 100%; @@ -9,13 +9,13 @@ } @media (--reduce-motion) { - .ActionSheet { + .host { --vkui_internal--actionsheet_animation_opacity_initial: 0; --vkui_internal--actionsheet_animation_translateY_initial: 0%; } } -.ActionSheet__content-wrapper { +.contentWrapper { padding-block: 8px; padding-inline: 0; overflow: hidden; @@ -24,41 +24,41 @@ background: var(--vkui--color_background_modal); } -.ActionSheet__header { +.header { color: var(--vkui--color_text_subhead); padding-block: 16px; padding-inline: 16px; } -.ActionSheet--opening { +.opening { /* prettier-ignore */ animation: animation-actionsheet-slide-up var(--vkui--animation_duration_m) var(--vkui--animation_easing_platform); } -.ActionSheet--closing { +.closing { opacity: 0; /* prettier-ignore */ animation: animation-actionsheet-slide-down var(--vkui--animation_duration_m) var(--vkui--animation_easing_platform); } -.ActionSheet__title + .ActionSheet__text { +.title + .text { margin-block-start: 8px; } /** * iOS */ -.ActionSheet--ios { +.ios { padding: 8px; background: transparent; } -.ActionSheet--ios .ActionSheet__content-wrapper { +.ios .contentWrapper { border-radius: 14px; padding: 0; } -.ActionSheet__close-item-wrapper--ios { +.closeItemWrapperIos { margin-block: 8px var(--vkui_internal--safe_area_inset_bottom); overflow: hidden; border-radius: 14px; @@ -66,24 +66,24 @@ background: var(--vkui--color_background_modal); } -.ActionSheet--ios.ActionSheet--closing { +.ios.closing { transform: translateY(100%); } -.ActionSheet--ios .ActionSheet__header { +.ios .header { position: relative; text-align: center; } -.ActionSheet--ios .ActionSheet__title { +.ios .title { position: relative; } -.ActionSheet--ios .ActionSheet__text { +.ios .text { position: relative; } -.ActionSheet--ios .ActionSheet__header::before { +.ios .header::before { position: absolute; inset-inline-start: 0; inset-block-start: 0; @@ -97,7 +97,7 @@ * Compact */ -.ActionSheet--sizeY-compact .ActionSheet__header { +.sizeYCompact .header { padding-block: 12px; } @@ -105,7 +105,7 @@ * Desktop version */ -.ActionSheet--menu { +.menu { --vkui_internal--actionsheet_animation_opacity_initial: 0; --vkui_internal--actionsheet_animation_translateY_initial: 5%; @@ -115,13 +115,13 @@ max-inline-size: 100%; } -.ActionSheet--menu.ActionSheet--ios .ActionSheet__content-wrapper { +.menu.ios .contentWrapper { border-radius: 14px; padding: 0; } @media (--reduce-motion) { - .ActionSheet--menu { + .menu { --vkui_internal--actionsheet_animation_translateY_initial: 0%; } } diff --git a/packages/vkui/src/components/ActionSheet/ActionSheet.test.tsx b/packages/vkui/src/components/ActionSheet/ActionSheet.test.tsx index 1baf5cbd04..89f8e99d59 100644 --- a/packages/vkui/src/components/ActionSheet/ActionSheet.test.tsx +++ b/packages/vkui/src/components/ActionSheet/ActionSheet.test.tsx @@ -197,9 +197,7 @@ describe(ActionSheet, () => { const result = render(); await waitForFloatingPosition(); await userEvent.click( - result.container.querySelector( - `.${popoutWrapperStyles['PopoutWrapper__overlay']}`, - )!, + result.container.querySelector(`.${popoutWrapperStyles.overlay}`)!, ); await waitCSSKeyframesAnimation(result.getByRole('dialog'), { runOnlyPendingTimers: true }); expect(onClose).toHaveBeenCalledTimes(1); diff --git a/packages/vkui/src/components/ActionSheet/ActionSheet.tsx b/packages/vkui/src/components/ActionSheet/ActionSheet.tsx index 787dee3c40..c81bd2915f 100644 --- a/packages/vkui/src/components/ActionSheet/ActionSheet.tsx +++ b/packages/vkui/src/components/ActionSheet/ActionSheet.tsx @@ -111,21 +111,21 @@ export const ActionSheet = ({ className={mode === 'menu' ? className : undefined} style={mode === 'menu' ? style : undefined} > -
+
{(header || text) && ( -
+
{header && ( - + {header} )} - {text && {text}} + {text && {text}}
)} {children}
{platform === 'ios' && mode === 'sheet' && ( -
+
{iosCloseItem ?? }
)} diff --git a/packages/vkui/src/components/ActionSheet/ActionSheetDropdownMenu.tsx b/packages/vkui/src/components/ActionSheet/ActionSheetDropdownMenu.tsx index d91133ede5..b2e9906cb9 100644 --- a/packages/vkui/src/components/ActionSheet/ActionSheetDropdownMenu.tsx +++ b/packages/vkui/src/components/ActionSheet/ActionSheetDropdownMenu.tsx @@ -71,11 +71,11 @@ export const ActionSheetDropdownMenu = ({ offsetByMainAxis={popupOffsetDistance} placement={placement} className={classNames( - styles['ActionSheet'], - platform === 'ios' && styles['ActionSheet--ios'], - styles['ActionSheet--menu'], - closing ? styles['ActionSheet--closing'] : styles['ActionSheet--opening'], - sizeY === 'compact' && styles['ActionSheet--sizeY-compact'], + styles.host, + platform === 'ios' && styles.ios, + styles.menu, + closing ? styles.closing : styles.opening, + sizeY === 'compact' && styles.sizeYCompact, className, )} style={style} diff --git a/packages/vkui/src/components/ActionSheet/ActionSheetDropdownSheet.tsx b/packages/vkui/src/components/ActionSheet/ActionSheetDropdownSheet.tsx index 8ed571f371..3e9e67ecae 100644 --- a/packages/vkui/src/components/ActionSheet/ActionSheetDropdownSheet.tsx +++ b/packages/vkui/src/components/ActionSheet/ActionSheetDropdownSheet.tsx @@ -29,10 +29,10 @@ export const ActionSheetDropdownSheet = ({ {...restProps} onClick={stopPropagation} className={classNames( - styles['ActionSheet'], - platform === 'ios' && styles['ActionSheet--ios'], - closing ? styles['ActionSheet--closing'] : styles['ActionSheet--opening'], - sizeY === 'compact' && styles['ActionSheet--sizeY-compact'], + styles.host, + platform === 'ios' && styles.ios, + closing ? styles.closing : styles.opening, + sizeY === 'compact' && styles.sizeYCompact, className, )} > diff --git a/packages/vkui/src/components/ActionSheetItem/ActionSheetItem.module.css b/packages/vkui/src/components/ActionSheetItem/ActionSheetItem.module.css index cffc15f91b..116a184980 100644 --- a/packages/vkui/src/components/ActionSheetItem/ActionSheetItem.module.css +++ b/packages/vkui/src/components/ActionSheetItem/ActionSheetItem.module.css @@ -1,4 +1,4 @@ -.ActionSheetItem { +.host { position: relative; overflow: hidden; display: flex; @@ -11,82 +11,82 @@ box-sizing: border-box; } -.ActionSheetItem__container, -.ActionSheetItem__before, -.ActionSheetItem__after { +.container, +.before, +.after { position: relative; } -.ActionSheetItem__before, -.ActionSheetItem__after { +.before, +.after { flex-shrink: 0; } -.ActionSheetItem__container { +.container { max-inline-size: 100%; flex-grow: 1; padding-block: 10px; padding-inline: 0; } -.ActionSheetItem--ellipsis { +.ellipsis { overflow: hidden; white-space: nowrap; } -.ActionSheetItem__content { +.content { display: flex; align-items: baseline; overflow: hidden; justify-content: space-between; } -.ActionSheetItem--ellipsis .ActionSheetItem__content { +.ellipsis .content { justify-content: flex-start; } -.ActionSheetItem__children { +.children { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; } -.ActionSheetItem__subtitle { +.subtitle { color: var(--vkui--color_text_secondary); overflow: hidden; text-overflow: ellipsis; margin-block-start: 2px; } -.ActionSheetItem__meta { +.meta { color: var(--vkui--color_text_secondary); margin-inline-start: 6px; flex-shrink: 0; } -.ActionSheetItem__before { +.before { color: var(--vkui--color_icon_accent); margin-inline-end: 16px; } -.ActionSheetItem--menu .ActionSheetItem__before { +.menu .before { margin-inline-end: 12px; } -.ActionSheetItem--ios .ActionSheetItem__before { +.ios .before { color: var(--vkui--color_icon_accent_themed); margin-inline-end: 18px; } -.ActionSheetItem--mode-destructive .ActionSheetItem__container:first-child { +.modeDestructive .container:first-child { color: var(--vkui--color_text_negative); } -.ActionSheetItem--mode-destructive .ActionSheetItem__before { +.modeDestructive .before { color: var(--vkui--color_icon_negative); } -.ActionSheetItem__after { +.after { display: flex; flex-direction: row; margin-inline-start: 16px; @@ -94,7 +94,7 @@ } /* stylelint-disable-next-line selector-max-universal -- gap 12px */ -.ActionSheetItem__after > *:not(:last-child) { +.after > *:not(:last-child) { margin-inline-end: 12px; } @@ -102,7 +102,7 @@ * iOS */ -.ActionSheetItem--ios { +.ios { padding-block: 14px; padding-inline: 18px; min-block-size: 56px; @@ -110,20 +110,15 @@ background: var(--vkui--color_background_modal); } -.ActionSheetItem--rich { +.rich { color: var(--vkui--color_text_primary); } -.ActionSheet--ios.ActionSheetItem--selectable { - padding-block: 14px; - padding-inline: 16px; -} - -.ActionSheetItem--ios.ActionSheetItem--mode-destructive { +.ios.modeDestructive { color: var(--vkui--color_text_negative); } -.ActionSheetItem--ios::before { +.ios::before { position: absolute; inset-inline-start: 0; inset-block-start: 0; @@ -136,7 +131,7 @@ background-color 0.15s ease-out; } -.ActionSheetItem--ios::after { +.ios::after { position: absolute; inset-inline: 0; block-size: 1px; @@ -147,38 +142,36 @@ } @media (min-resolution: 2dppx) { - .ActionSheetItem--ios::after { + .ios::after { transform: scaleY(0.5); } } @media (min-resolution: 3dppx) { - .ActionSheetItem--ios::after { + .ios::after { transform: scaleY(0.33); } } -.ActionSheetItem--ios:first-child::after, -.ActionSheetItem--mode-cancel::after, -.ActionSheet--ios .ActionSheetItem--last::after, -.ActionSheet--ios .ActionSheet__header:empty { +.ios:first-child::after, +.modeCancel::after { content: none; } -.ActionSheetItem--ios.ActionSheetItem--active::before { +.ios.active::before { background-color: var(--vkui--color_transparent--active); opacity: 1; transition: none; } -.ActionSheetItem--ios .ActionSheetItem__container { +.ios .container { padding: 0; } -.ActionSheetItem--ios .ActionSheetItem--centered { +.ios .centered { justify-content: center; } -.ActionSheetItem--mode-cancel { +.modeCancel { min-block-size: 52px; } @@ -186,7 +179,7 @@ * Desktop */ -.ActionSheetItem--menu { +.menu { inline-size: auto; cursor: pointer; border-radius: 0; @@ -196,14 +189,14 @@ * Disabled */ -.ActionSheetItem[disabled] .ActionSheetItem__children, -.ActionSheetItem[disabled] .ActionSheetItem__meta, -.ActionSheetItem[disabled] .ActionSheetItem__subtitle { +.host[disabled] .children, +.host[disabled] .meta, +.host[disabled] .subtitle { color: var(--vkui--color_text_secondary); } -.ActionSheetItem[disabled] .ActionSheetItem__marker, -.ActionSheetItem[disabled] .ActionSheetItem__before { +.host[disabled] .marker, +.host[disabled] .before { color: var(--vkui--color_icon_secondary); } @@ -211,22 +204,21 @@ * SizeY = compact */ -.ActionSheetItem--sizeY-compact { +.sizeYCompact { min-block-size: 32px; } -.ActionSheetItem--sizeY-compact.ActionSheetItem--ios { +.sizeYCompact.ios { min-block-size: 36px; padding-block: 4px; padding-inline: 18px 20px; } -.ActionSheetItem--sizeY-compact:not(.ActionSheetItem--ios) - + .ActionSheetItem--sizeY-compact:not(.ActionSheetItem--ios) { +.sizeYCompact:not(.ios) + .sizeYCompact:not(.ios) { margin-block-start: 2px; } -.ActionSheetItem--sizeY-compact .ActionSheetItem__container { +.sizeYCompact .container { padding-block: 5px; padding-inline: 0; } diff --git a/packages/vkui/src/components/ActionSheetItem/ActionSheetItem.tsx b/packages/vkui/src/components/ActionSheetItem/ActionSheetItem.tsx index d9a1fcf96a..814458c5f8 100644 --- a/packages/vkui/src/components/ActionSheetItem/ActionSheetItem.tsx +++ b/packages/vkui/src/components/ActionSheetItem/ActionSheetItem.tsx @@ -127,50 +127,39 @@ export const ActionSheetItem = ({ {...(Component && { Component })} {...restProps} onClick={onItemClickImpl} - activeMode={platform === 'ios' ? styles['ActionSheetItem--active'] : undefined} + activeMode={platform === 'ios' ? styles.active : undefined} className={classNames( - styles['ActionSheetItem'], - platform === 'ios' && styles['ActionSheetItem--ios'], - mode === 'cancel' && styles['ActionSheetItem--mode-cancel'], - mode === 'destructive' && styles['ActionSheetItem--mode-destructive'], - sizeY === 'compact' && styles['ActionSheetItem--sizeY-compact'], - isRich && styles['ActionSheetItem--rich'], - actionSheetMode === 'menu' && styles['ActionSheetItem--menu'], - selectable && styles['ActionSheetItem--selectable'], + styles.host, + platform === 'ios' && styles.ios, + mode === 'cancel' && styles.modeCancel, + mode === 'destructive' && styles.modeDestructive, + sizeY === 'compact' && styles.sizeYCompact, + isRich && styles.rich, + actionSheetMode === 'menu' && styles.menu, className, )} onKeyDown={onKeyDown} > - {before &&
{before}
} -
-
+ {before &&
{before}
} +
+
{platform === 'ios' ? ( {children} ) : ( - {children} + {children} )} - {meta && {meta}} + {meta && {meta}}
- {subtitle && {subtitle}} + {subtitle && {subtitle}}
{(selectable || after) && ( -
+
{after} {selectable && ( {children} diff --git a/packages/vkui/src/components/Alert/Alert.module.css b/packages/vkui/src/components/Alert/Alert.module.css index 5eed13f4e5..ad30ad911f 100644 --- a/packages/vkui/src/components/Alert/Alert.module.css +++ b/packages/vkui/src/components/Alert/Alert.module.css @@ -1,4 +1,4 @@ -.Alert { +.host { --vkui_internal--alert_animation_scale_initial: 0.95; user-select: none; @@ -12,22 +12,22 @@ border-radius: var(--vkui--size_border_radius_paper--regular); } -.Alert--desktop { +.desktop { inline-size: 100%; } -.Alert--opening { +.opening { /* prettier-ignore */ animation: animation-alert-scale-up var(--vkui--animation_duration_m) var(--vkui--animation_easing_platform); } -.Alert--closing { +.closing { opacity: 0; /* prettier-ignore */ animation: animation-alert-scale-down var(--vkui--animation_duration_m) var(--vkui--animation_easing_platform); } -.Alert::before { +.host::before { position: absolute; inset-inline-start: 0; inset-block-start: 0; @@ -38,22 +38,22 @@ border-radius: inherit; } -.Alert__content { +.content { position: relative; padding-block: 24px 16px; padding-inline: 24px; } -.Alert__content--withButton { +.contentWithButton { padding-inline-end: 52px; } -.Alert__action { +.action { white-space: nowrap; background: transparent; } -.Alert__actions { +.actions { display: flex; max-inline-size: 100%; position: relative; @@ -61,44 +61,44 @@ padding-inline: 12px; } -.Alert__header { +.header { margin-block-end: 8px; } -.Alert__text { +.text { color: var(--vkui--color_text_secondary); } -.Alert__header, -.Alert__text { +.header, +.text { word-break: break-word; } -.Alert__actions--direction-horizontal { +.actionsDirectionHorizontal { justify-content: flex-end; } -.Alert__actions--direction-vertical { +.actionsDirectionVertical { flex-direction: column; align-items: flex-end; } /* stylelint-disable-next-line selector-max-universal */ -.Alert__actions > * { +.actions > * { margin: 4px; } -.Alert__actions--align-left { +.actionsAlignLeft { justify-content: flex-start; align-items: flex-start; } -.Alert__actions--align-center { +.actionsAlignCenter { justify-content: center; align-items: center; } -.Alert__actions--align-right { +.actionsAlignRight { justify-content: flex-end; align-items: flex-end; } @@ -106,7 +106,7 @@ /** * iOS version */ -.Alert--ios { +.ios { --vkui_internal--alert_animation_scale_initial: 1.05; inline-size: 270px; @@ -115,25 +115,25 @@ box-shadow: none; } -.Alert--ios.Alert--closing { +.ios.closing { --vkui_internal--alert_animation_scale_initial: 1; } -.Alert--ios .Alert__content { +.ios .content { padding-block: 20px; padding-inline: 16px; text-align: center; } -.Alert--ios .Alert__header:not(:last-child) { +.ios .header:not(:last-child) { margin-block-end: 7px; } -.Alert--ios .Alert__text { +.ios .text { color: inherit; } -.Alert--ios .Alert__content::after { +.ios .content::after { content: ''; position: absolute; inset-block-end: 0; @@ -143,16 +143,16 @@ transform-origin: center bottom; } -.Alert--ios .Alert__actions { +.ios .actions { padding: initial; } -.Alert--ios .Alert__actions--direction-vertical { +.ios .actionsDirectionVertical { flex-direction: column; align-items: initial; } -.Alert--ios .Alert__action { +.ios .action { font-family: var(--vkui--font_family_base); position: relative; font-size: 17px; @@ -171,13 +171,13 @@ border-radius: 0; } -.Alert--ios .Alert__action::after { +.ios .action::after { content: ''; position: absolute; background: var(--vkui--color_separator_primary_alpha); } -.Alert--ios .Alert__actions--direction-horizontal .Alert__action::after { +.ios .actionsDirectionHorizontal .action::after { inset-block-start: 0; inset-inline-end: 0; inline-size: 1px; @@ -185,25 +185,25 @@ transform-origin: right center; } -.Alert--ios .Alert__actions--direction-horizontal .Alert__action:last-child::after { +.ios .actionsDirectionHorizontal .action:last-child::after { content: none; } -.Alert--ios .Alert__actions--direction-horizontal .Alert__action { +.ios .actionsDirectionHorizontal .action { flex-grow: 1; flex-shrink: 1; flex-basis: 0; } -.Alert--ios .Alert__actions--direction-horizontal .Alert__action:first-child { +.ios .actionsDirectionHorizontal .action:first-child { border-end-start-radius: var(--vkui--size_border_radius_paper--regular); } -.Alert--ios .Alert__actions--direction-horizontal .Alert__action:last-child { +.ios .actionsDirectionHorizontal .action:last-child { border-end-end-radius: var(--vkui--size_border_radius_paper--regular); } -.Alert--ios .Alert__actions--direction-vertical .Alert__action::after { +.ios .actionsDirectionVertical .action::after { inset-inline-start: 0; inset-block-end: 0; inline-size: 100%; @@ -211,11 +211,11 @@ transform-origin: center bottom; } -.Alert--ios .Alert__actions--direction-vertical .Alert__action:last-child::after { +.ios .actionsDirectionVertical .action:last-child::after { content: none; } -.Alert--ios .Alert__actions--direction-vertical .Alert__action:last-child { +.ios .actionsDirectionVertical .action:last-child { border-start-start-radius: 0; border-start-end-radius: 0; border-end-end-radius: 12px; @@ -223,31 +223,31 @@ } @media (min-resolution: 2dppx) { - .Alert--ios .Alert__content::after, - .Alert--ios .Alert__actions--direction-vertical .Alert__action::after { + .ios .content::after, + .ios .actionsDirectionVertical .action::after { transform: scaleY(0.5); } - .Alert--ios .Alert__actions--direction-horizontal .Alert__action::after { + .ios .actionsDirectionHorizontal .action::after { transform: scaleX(0.5); } } @media (min-resolution: 3dppx) { - .Alert--ios .Alert__content::after, - .Alert--ios .Alert__actions--direction-vertical .Alert__action::after { + .ios .content::after, + .ios .actionsDirectionVertical .action::after { transform: scaleY(0.33); } - .Alert--ios .Alert__actions--direction-horizontal .Alert__action::after { + .ios .actionsDirectionHorizontal .action::after { transform: scaleX(0.33); } } -.Alert--ios .Alert__action--mode-cancel { +.ios .actionModeCancel { font-weight: 600; } -.Alert--ios .Alert__action--mode-destructive { +.ios .actionModeDestructive { color: var(--vkui--color_text_negative); } @@ -255,31 +255,31 @@ * VKCOM */ -.Alert--vkcom { +.vkcom { box-shadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 0 96px rgba(0, 0, 0, 0.16); inline-size: 400px; } -.Alert--vkcom .Alert__content { +.vkcom .content { padding-block-end: 20px; } -.Alert--vkcom .Alert__actions { +.vkcom .actions { padding-block: 0 12px; padding-inline: 20px; } -.Alert--vkcom .Alert__button { +.vkcom .button { order: 2; } -.Alert--vkcom .Alert__button--mode-cancel { +.vkcom .buttonModeCancel { order: 1; } -.Alert__dismiss { +.dismiss { position: absolute; color: var(--vkui--color_icon_secondary); inset-block-start: 8px; @@ -287,8 +287,8 @@ } @media (--reduce-motion) { - .Alert, - .Alert--ios { + .host, + .ios { --vkui_internal--alert_animation_scale_initial: 1; } } diff --git a/packages/vkui/src/components/Alert/Alert.test.tsx b/packages/vkui/src/components/Alert/Alert.test.tsx index 4b6641dfcf..bcdd5dd0b2 100644 --- a/packages/vkui/src/components/Alert/Alert.test.tsx +++ b/packages/vkui/src/components/Alert/Alert.test.tsx @@ -15,6 +15,8 @@ import { ConfigProvider } from '../ConfigProvider/ConfigProvider'; import { Alert, type AlertProps } from './Alert'; import styles from './Alert.module.css'; import buttonStyles from '../Button/Button.module.css'; +import modalDismissButtonStyles from '../ModalDismissButton/ModalDismissButton.module.css'; +import popoutWrapperStyles from '../PopoutWrapper/PopoutWrapper.module.css'; import captionStyles from '../Typography/Caption/Caption.module.css'; import footnoteStyles from '../Typography/Footnote/Footnote.module.css'; import titleStyles from '../Typography/Title/Title.module.css'; @@ -36,10 +38,10 @@ describe('Alert', () => { , ); - const target = - trigger === 'overlay' ? '.vkuiPopoutWrapper__overlay' : '.vkuiModalDismissButton'; + const className = + trigger === 'overlay' ? popoutWrapperStyles.overlay : modalDismissButtonStyles.host; - await userEvent.click(document.querySelector(target)!); + await userEvent.click(document.querySelector(`.${className}`)!); await waitCSSKeyframesAnimation(result.getByRole('alertdialog'), { runOnlyPendingTimers: true, }); @@ -192,11 +194,11 @@ describe('Alert', () => { it.each([ { mode: 'destructive' as const, - className: styles['Alert__action--mode-destructive'], + className: styles.actionModeDestructive, }, { mode: 'cancel' as const, - className: styles['Alert__action--mode-cancel'], + className: styles.actionModeCancel, }, ])( 'should have className "$className" with mode "$mode" in IOS platform', @@ -219,13 +221,13 @@ describe('Alert', () => { it.each([ { mode: 'cancel' as const, - className: styles['Alert__button--mode-cancel'], - buttonClassName: buttonStyles['Button--mode-secondary'], + className: styles.buttonModeCancel, + buttonClassName: buttonStyles.modeSecondary, }, { mode: 'destructive' as const, className: undefined, - buttonClassName: buttonStyles['Button--mode-primary'], + buttonClassName: buttonStyles.modePrimary, }, ])( 'should have className "$className" ans "$buttonClassName" with mode "$mode" in VKCOM platform', @@ -269,15 +271,15 @@ describe('Alert', () => { it.each([ { align: 'left' as const, - className: styles['Alert__actions--align-left'], + className: styles.actionsAlignLeft, }, { align: 'center' as const, - className: styles['Alert__actions--align-center'], + className: styles.actionsAlignCenter, }, { align: 'right' as const, - className: styles['Alert__actions--align-right'], + className: styles.actionsAlignRight, }, ])( 'actions wrapper should have className "$className" when use align "$align"', @@ -286,7 +288,7 @@ describe('Alert', () => { await waitCSSKeyframesAnimation(result.getByRole('alertdialog'), { runOnlyPendingTimers: true, }); - const actionsWrapper = getDocumentBody().getElementsByClassName(styles['Alert__actions'])[0]; + const actionsWrapper = getDocumentBody().getElementsByClassName(styles.actions)[0]; expect(actionsWrapper).toHaveClass(className); }, ); @@ -294,23 +296,23 @@ describe('Alert', () => { it.each([ { header: 'Header', - headerClassNames: [titleStyles['Title--level-3'], typographyStyles['Typography--weight-1']], + headerClassNames: [titleStyles.level3, typographyStyles.weight1], text: 'Text', - textClassNames: [captionStyles['Caption--level-1']], + textClassNames: [captionStyles.level1], platform: Platform.IOS, }, { header: 'Header', - headerClassNames: [titleStyles['Title--level-2'], typographyStyles['Typography--weight-2']], + headerClassNames: [titleStyles.level2, typographyStyles.weight2], text: 'Text', - textClassNames: [footnoteStyles['Footnote']], + textClassNames: [footnoteStyles.host], platform: Platform.VKCOM, }, { header: 'Header', - headerClassNames: [titleStyles['Title--level-2'], typographyStyles['Typography--weight-2']], + headerClassNames: [titleStyles.level2, typographyStyles.weight2], text: 'Text', - textClassNames: [typographyStyles['Typography--weight-3']], + textClassNames: [typographyStyles.weight3], platform: Platform.ANDROID, }, ])( @@ -324,8 +326,8 @@ describe('Alert', () => { await waitCSSKeyframesAnimation(result.getByRole('alertdialog'), { runOnlyPendingTimers: true, }); - const headerElement = result.container.getElementsByClassName(styles['Alert__header'])[0]; - const textElement = result.container.getElementsByClassName(styles['Alert__text'])[0]; + const headerElement = result.container.getElementsByClassName(styles.header)[0]; + const textElement = result.container.getElementsByClassName(styles.text)[0]; headerClassNames.forEach((className) => expect(headerElement).toHaveClass(className)); textClassNames.forEach((className) => expect(textElement).toHaveClass(className)); diff --git a/packages/vkui/src/components/Alert/Alert.tsx b/packages/vkui/src/components/Alert/Alert.tsx index afb0957773..bc07672082 100644 --- a/packages/vkui/src/components/Alert/Alert.tsx +++ b/packages/vkui/src/components/Alert/Alert.tsx @@ -147,11 +147,11 @@ export const Alert = ({ onClose={close} autoFocus={animationState === 'entered'} className={classNames( - styles['Alert'], - platform === 'ios' && styles['Alert--ios'], - platform === 'vkcom' && styles['Alert--vkcom'], - closing ? styles['Alert--closing'] : styles['Alert--opening'], - isDesktop && styles['Alert--desktop'], + styles.host, + platform === 'ios' && styles.ios, + platform === 'vkcom' && styles.vkcom, + closing ? styles.closing : styles.opening, + isDesktop && styles.desktop, )} role="alertdialog" aria-modal @@ -160,8 +160,8 @@ export const Alert = ({ >
{hasReactNode(header) && {header}} @@ -170,7 +170,7 @@ export const Alert = ({ {isDismissButtonVisible && dismissButtonMode === 'inside' && ( { @@ -39,10 +39,7 @@ const AlertActionBase = ({ mode, ...restProps }: AlertActionProps) => { return (