Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(Modal, MiniModal, SidePage): update mobile version and fix some bugs #3632

Open
wants to merge 11 commits into
base: next
Choose a base branch
from

Conversation

SchwJ
Copy link
Member

@SchwJ SchwJ commented Apr 1, 2025

Проблема

Дизайн Modal, MiniModal, SidePage на мобилке устарел. Необходимо обновить дизайн до состояния макетов.

Решение

Обновила дизайн согласно макетам.
По пути всплывали мелкие баги в модалке, попутно фиксила их.

Ссылки

IF-2125
Макеты адаптивности для модалки и сайдпейжда
Тред с обсуждением задачи

Чек-лист перед запросом ревью

  1. Добавлены тесты на все изменения
    ⬜ unit-тесты для логики
    ✅ скриншоты для верстки и кросс-браузерности
    ⬜ нерелевантно

  2. Добавлена (обновлена) документация
    ✅ styleguidist для пропов и примеров использования компонентов
    ⬜ jsdoc для утилит и хелперов
    ⬜ комментарии для неочевидных мест в коде
    ⬜ прочие инструкции (README.md, contributing.md и др.)
    ⬜ нерелевантно

  3. Изменения корректно типизированы
    ✅ без использования any (см. PR 2856)
    ⬜ нерелевантно

  4. Прочее
    ⬜ все тесты и линтеры на CI проходят
    ✅ в коде нет лишних изменений
    ✅ заголовок PR кратко и доступно отражает суть изменений (он попадет в changelog)

@SchwJ SchwJ changed the title Mobile modal and sidepage chore(Modal, MiniModal, SidePage): update mobile version and fix some bugs Apr 1, 2025
@@ -14,7 +14,6 @@ export const getMiniModalTheme = (contextTheme: Theme, propsTheme: ThemeIn = {})
mobileModalFooterPadding: theme.miniModalFooterPaddingMobile,
mobileModalHeaderPadding: theme.miniModalHeaderPaddingMobile,
mobileModalBodyPadding: theme.miniModalBodyPaddingMobile,
mobileModalContainerHeight: theme.miniModalHeightMobile,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Эта переменная нигде не использовалась

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А разве не в классе containerMobile у Modal?


containerDesktop() {
Copy link
Member Author

@SchwJ SchwJ Apr 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вынесла в отдельный стиль, так как &::before нужен только в старой версии ИЛИ в текущей версии на десктопе

@@ -111,7 +178,6 @@ export const styles = memoizeStyle({
display: flex;
right: ${padding}px;
top: ${padding}px;
background: none;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Сразу после идет присвоение background: transparent;, так что эту строку можно убрать

@@ -11,12 +11,6 @@ export const styles = memoizeStyle({
`;
},

mobileRoot() {
Copy link
Member Author

@SchwJ SchwJ Apr 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Убрала, так как на мобилке ширина должна быть либо передана пропом mobileWidth или быть равной 100%. Вынесла 100% из стилей в SidePage.tsx, чтобы логика выбора ширины была в одном месте

@@ -177,7 +182,7 @@ export class SidePage extends React.Component<SidePageProps, SidePageState> {
wrapperRef={this.rootRef}
style={{ position: 'absolute' }}
>
{blockBackground && this.renderShadow(isMobile)}
{blockBackground && (versionGTE5_2 || !isMobile) && this.renderShadow()}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Теперь согласно дизайну вуалька должна быть не только на десктопе, но и на мобилках

Copy link
Member Author

@SchwJ SchwJ Apr 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Добавила Math.Ceil, потому что иногда разделитель между хедером и контентом рисовался, когда его быть не должно. Оказалось, что это из-за того, что top меньше offset на 0,001

Например, в этом примере разделитель не должен отображаться, но он отображается
image

@sashachabin sashachabin self-requested a review April 2, 2025 09:23
<br />
<br />
<br />
<br />А
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А? 😁

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Блин, да, надо в этом месте улучшить историю

const { offset, role } = this.getProps();

const getWidth = isMobile ? mobileWidth || '100%' : width || (blockBackground ? 800 : 500);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А давай запишем без тернарника, очень сложно читать 2 тернарника подряд + ||

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Попробуй сделать 1 тернарник и вынести значения с || в переменные, либо конструкцию из if. Переносы строк тоже бы помогли

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А ещё это не функция, давай уберем действие get из названия

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Как думаешь, так ок? Есть сложности с наименованием)
image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Мб ещё подумаем, но так точно лучше!

position: absolute;
padding: ${t.mobileModalWithoutHeaderCloseButtonPadding};
margin: -${t.mobileModalWithoutHeaderCloseButtonPadding};
vertical-align: top;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Хм, кажется, что это не сработает, если мы убрали этот элемент из потока

versionGTE5_2 &&
(mobileAppearance === 'fullscreen-spacing' || (mobileAppearance === 'auto' && hasFooter)),
[styles.mobileCenterContainerFullscreen5_2()]:
isMobile && versionGTE5_2 && mobileAppearance === 'fullscreen',
Copy link
Member

@sashachabin sashachabin Apr 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Небольшое пожелание: у тебя выше хороший порядок — сначала versionGTE5_2, а потом isMobile. Давай тут и в других местах так же сделаем, чтобы дальше было проще читать/рефакторить

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Везде вынесла versionGTE5_2 вперед

interface ModalWithVariableHeightState {
isOpened: boolean;
}
class MobileModalWithSettings extends React.Component<SuperModalProps> {
Copy link
Member

@sashachabin sashachabin Apr 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Давай немного приберёмся

Было Стало
image image
render(() => {
  const [opened, setOpened] = React.useState(false);
  const [position, setPosition] = React.useState("auto");
  const [hasHeader, setHasHeader] = React.useState(false);
  const [stickyHeader, setStickyHeader] = React.useState(false);
  const header = <Modal.Header sticky={stickyHeader}>Title</Modal.Header>;
  const [hasFooter, setHasFooter] = React.useState(false);
  const [stickyFooter, setStickyFooter] = React.useState(false);
  const [showSecondButton, setShowSecondButton] = React.useState(false);
  const footer = (
    <ResponsiveLayout>
      {({ isMobile }) => {
        return (
          <Modal.Footer sticky={stickyFooter}>
            <Button
              style={
                isMobile
                  ? {
                    width: "100%",
                  }
                  : undefined
              }
              onClick={() => setShowSecondButton(!showSecondButton)}
            >
              show/hide second button
            </Button>
            {showSecondButton && (
              <Button
                style={
                  isMobile
                    ? {
                      width: "100%",
                    }
                    : undefined
                }
              >
                i'm second button
              </Button>
            )}
          </Modal.Footer>
        );
      }}
    </ResponsiveLayout>
  );
  const [isLongContent, setIsLongContent] = React.useState(false);
  const body = isLongContent ? (
    <Modal.Body>
      <div>
        * Навигация – используйте верхнее меню для быстрого перехода к основным
        разделам.
        <br />
        * Фильтрация и поиск – применяйте фильтры и строку поиска, чтобы быстрее
        находить нужные данные. <br />
        * Настройки – персонализируйте интерфейс под свои нужды, изменяя темы,
        уведомления и другие параметры. <br />
        ❓ Часто задаваемые вопросы: <br />
        🔹 Как сохранить изменения? – После внесения правок нажмите кнопку
        «Сохранить». Все данные обновятся автоматически. <br />
        🔹 Можно ли отменить действие? – Да! Используйте кнопку «Отмена» или
        сочетание клавиш Ctrl + Z (для ПК). <br />
        🔗 Дополнительные ресурсы: Если у вас возникли вопросы или сложности, вы
        можете: <br />
        * Ознакомиться с подробной документацией (доступна в разделе «Помощь»).{" "}
        <br />
        * Обратиться в техническую поддержку через чат или по email. <br />*
        Посетить форум сообщества, где пользователи делятся советами и
        решениями. Спасибо, что пользуетесь нашим сервисом! 🚀
        <br />
        <br />
        <br />
        <br />
        <br />
        <br />
        <br />
        <br />
        <br />
        <br />
        <br />
        <br />
        <br />
        <br />
        <br />
        <br />
        <br />
        <br />
        <br />
        <br />
        <br />
        <br />А
      </div>
    </Modal.Body>
  ) : (
    <Modal.Body>
      Текст сообщения в лайтбоксе, достаточно длинный, чтобы занять несколько
      строк. Если в лайтбоксе только это сообщение и нет других контролов, то
      лайтбокс не нуждается в терминальной плашке и может даже не содержать
      кнопок.
    </Modal.Body>
  );
  function renderModal() {
    return (
      <ThemeContext.Provider
        value={ThemeFactory.create({
          mobileMediaQuery: "(max-width: 576px)",
        })}
      >
        <Modal mobileAppearance={position} onClose={() => setOpened(false)}>
          {hasHeader && header}
          {body}
          {hasFooter && footer}
        </Modal>
      </ThemeContext.Provider>
    );
  }
  return (
    <Gapped gap={16} vertical>
      {opened && renderModal()}
      <Gapped verticalAlign="top" gap={48}>
        <div>
          <b>Отображение mobileAppearance</b>
          <br />
          <br />
          <RadioGroup
          items={[
            "auto",
            "top",
            "center",
            "bottom",
            "fullscreen-spacing",
            "fullscreen",
          ]}
          onValueChange={setPosition}
        />
        </div>
        <div>
          <b>Настройки</b>
          <br />
          <br />
          <Gapped vertical gap={0}>
          <Checkbox
            checked={hasHeader}
            onValueChange={setHasHeader}
            children={"Шапка"}
          />
          <Checkbox
            checked={hasFooter}
            onValueChange={setHasFooter}
            children={"Повал"}
          />
          <Checkbox
            checked={stickyHeader}
            onValueChange={setStickyHeader}
            children={"Залипаюшая шапка"}
          />
          <Checkbox
            checked={stickyFooter}
            onValueChange={setStickyFooter}
            children={"Залипающий подвал"}
          />
          <Checkbox
            checked={isLongContent}
            onValueChange={setIsLongContent}
            children={"Длинный текст для появления скролла"}
          />
        </Gapped>
        </div>
      </Gapped>
      <Button onClick={() => setOpened(true)}>Открыть</Button>
    </Gapped>
  );
});

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А я правильно понимаю, что доку нужно открыть в мобилке, чтобы проверить?

Если да, то нам бы что-нибудь придумать — хотя бы задизаблить и предупредить :(

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Чтобы увидеть мобильную версию можно сделать экран поуже на десктопе. Для этого я написала в примере:

<ThemeContext.Provider
        value={ThemeFactory.create({
          mobileMediaQuery: "(max-width: 576px)",
        })}
      >

Copy link
Member Author

@SchwJ SchwJ Apr 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Взяла твой код, плюс написала комментарий, получилось вот так (это пример в доке)
image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не подумали, что с этой историей будут чаще взаимодействовать на узком экране, и получается там так:
image

Сделаю отображение второго столбца под первым

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вот так будет
image

@@ -57,6 +61,17 @@ export interface ModalProps

/** Задает объект с переменными темы. Он будет объединён с темой из контекста. */
theme?: ThemeIn;

/** Задает внешний вид модалки. Работает с версией темы >= 5_2.
* - `auto` — всегда показывать иконку очистки значения в заполненном поле
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут текст не тот что нужно

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Исправила

@@ -14,7 +14,6 @@ export const getMiniModalTheme = (contextTheme: Theme, propsTheme: ThemeIn = {})
mobileModalFooterPadding: theme.miniModalFooterPaddingMobile,
mobileModalHeaderPadding: theme.miniModalHeaderPaddingMobile,
mobileModalBodyPadding: theme.miniModalBodyPaddingMobile,
mobileModalContainerHeight: theme.miniModalHeightMobile,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А разве не в классе containerMobile у Modal?

Comment on lines 281 to 288
[styles.mobileCenterContainer()]: !versionGTE5_2 && isMobile,
[styles.mobileCenterContainer5_2(this.theme)]: versionGTE5_2 && isMobile,
[styles.mobileCenterContainerBig5_2(this.theme)]:
versionGTE5_2 &&
isMobile &&
(mobileAppearance === 'fullscreen-spacing' || (mobileAppearance === 'auto' && hasFooter)),
[styles.mobileCenterContainerFullscreen5_2()]:
versionGTE5_2 && isMobile && mobileAppearance === 'fullscreen',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут может тоже уже пора вынести логику в отдельную функцию?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Согласна, вынесла

@@ -247,14 +308,18 @@ export class Modal extends React.Component<ModalProps, ModalState> {
>
{!hasHeader && !noClose && (
<ZIndex
priority={1}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А зачем это понадобилось?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image
Приоритет в ZIndex'е для крестика я добавила для того, чтобы он был "выше" чем отступ у текста заголовка. Если priority={1} убрать, то ZIndex у крестика становится меньше, чем у текста заголовка, и тогда текст заголовка отображается поверх крестика и на него нельзя нажать

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

О каком заголовке речь? Это же сценарий без хедера. Или ты про контент модалки? А чем тогда вызвано это изменение в поведении?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ой, я оговорилась. Вместо "заголовка" должно быть "тело модалки (body)". Оно и выделено на скрине выше.

Приоритет в ZIndex'е для крестика я добавила для того, чтобы он был "выше" чем отступ у текста в body. Если priority={1} убрать, то ZIndex у крестика становится меньше, чем у текста тела модалки, и тогда отступ справа отображается поверх крестика и на крестик нельзя нажать

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants