PavelZubkov / react-extensible-components-experiments

Extensible react components[draft]
The Unlicense
0 stars 0 forks source link

Дизайн использования #1

Open PavelZubkov opened 4 years ago

PavelZubkov commented 4 years ago

ps: только дизайн, имплементация отдельно

самый первый набросок:


import React from "react";
import "./styles.css";

function Dialog(props) {
  return (
    <div class="Dialog">
      <div class="Dialog_header">
        <h1 class="Dialog_header_title">{props.dialog.title}</h1>
      </div>
      <div class="Dialog_body">
        How are you?
      </div>
      <div class="Dialog_footer">
        <button class="Dialog_footer_button_cancel">Cancel</button>
        <button class="Dialog_footer_button_ok">Ok</button>
      </div>
    </div>
  );
}
// Вместо диввов и баттонов будут использовать другие готовые компоненты либо html элементы
// Классы можно сгененрировать автоматически
// Для переопределения всего и вся нужно использовать похожий синтакис на бемовские шаблонизаторы
function Dialog(props) {
  return (
    <Card>
      <Card.Header>
        <Text.Title>Welcome<Text.Title />
      </Card.Header>
      <Card.Body>How are you?</Card.Body>
      <Card.Footer>
        <Button>Cancel</Button>
        <Button>Ok</Button>
      </Card.Footer>
    </Card>
  );
}

// На чем сошелся
function Dialog(props) {
  defineHelper(Dialog); // или декоратор.
  // Суть в том, что я описываю семантику обращаясь к свойствам этого же компонента
  return (
    <Dialog.Header as={Card.Header}>
      <Dialog.Header.Title as={Text.Title}>Welcome</Dialog.Header.Title>
    </Dialog.Header>
    <Dialog.Body>How are you?</Dialog.Body>
    <Dialog.Footer>
      <Dialog.Footer.Button.Cancel>Cancel</Dialog.Footer.Button.Cancel>
      <Dialog.Footer.Button.Ok>Ok</Dialog.Footer.Button.Ok>
    </Dialog.Footer>
  );
}

// Пока на этом варианте остановился
function Dialog(props) {
  const Dialog = useAdequateComponent(Dialog);
  return (
    <Dialog as={Card}>
      <Dialog.Header as={Card.Header}>
        <Dialog.Title as={Text.Title}>Welcome</Dialog.Title>
      </Dialog.Header>
      <Dialog.Body as={Card.Body}>How are you?</Dialog.Body>
      <Dialog.Footer as={Card.Footer}>
        <Dialog.Button> // если не передали as={} никакого элемента не будет отрендерено. Это что бы не писать Dialog.Button.Cancel, Dialog.Button.Ok
          <Dialog.Cancel as={Button}>Cancel</Dialog.Cancel>
          <Dialog.Ok as={Button}>Ok</Dialog.Ok>
        </Dialog.Button>
      </Dialog.Footer>
    </Dialog>
  );
}

const WelcomeDialog = Dialog.adequateExtend({
  footer_button_cancel: {
    $append: WelcomeIcon, // глянть бемовские шаблонизаторы и домовские методы
  }
});

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <Dialog
        header={{ title: 'ops' }}
        footer={{ button: { cancel: { children: 'Stop', onClick: () => alert('ops') } } }}
        footer={{ 'button.cancel': { chidlren: 'Strop', onClick: () => alert('ops') } }}
        footer_button_cancel_sub="Stop"
        footer_button_cancel_onClick={() => alert('ops')}
        footer_button_cancel={{ sub: 'Stop', onClick: () => alert('ops') }}
        footer_button_Cancel={<span>Click to OK!!!!#$</span>}
      />
    </div>
  );
}

upd: Расширение

/*
инструкция расширения состоит из двух частей: селектор и тело
селектор:
 селектор по структуре
  select(dialog_header).append(<Loader loading={props.isLoading} />)
 по блоку
  select(Card.Header).replace(null)
манипуляция узлам:
  append, prepend, before, after, replace, remove, wrap, removeWrapper
  ps: глянь что умеет jquery
перенаправление:
  (например dialog_header_title перенести в dialog_body)
манипуляция пропсами:
  например, было:
    <Dialog>
      <Dialog_Header>...
      <Dialog_Body>
      ...
  стало
    <Dialog>
      <Dialog_Header>{props.headerContent}</Dialog_Header>
      <Dialog_Body className={`type_${props.type}`}>
      ...и эти пропсы должны попасть в проп тайпсы компонента
  Надо осмыслить стоит ли полностью подражать бем или нет, кажется что это самый гибкий способ
  навешивать стили
  надо поразбираться в xslt возможно будет полезным
 */
Dialog0.adequateExtend((ext, props) => {
})
PavelZubkov commented 4 years ago

https://ru.bem.info/technologies/classic/bem-xjst/8/templates-syntax/ бемовский шаблонизатор. Предикат - это селектор, если проводить аналогию с css. Тело - инструкции по переопределению

в яндексе пробывали запилить react-bem-xjst http://webcache.googleusercontent.com/search?newwindow=1&safe=active&sxsrf=ALeKk00q_CdvAI_a8d6hHVA9JiWwqHpMXA%3A1595588684469&ei=TMAaX5KYHKOGwPAPhMqy4Ao&q=cache%3Ahttps%3A%2F%2Fru.bem.info%2Fforum%2F1212%2F&oq=cache%3Ahttps%3A%2F%2Fru.bem.info%2Fforum%2F1212%2F&gs_lcp=CgZwc3ktYWIQAzIECAAQQzIFCAAQywEyBQgAEMsBMgUIABDLATIFCAAQywEyBQgAEMsBMgUIABDLATIFCAAQywEyBQgAEMsBMgUIABDLAToECAAQRzoECCMQJzoFCAAQkQI6AggAOgIILjoICC4QxwEQowJQkGJYxXRgqXZoAXABeACAAZoBiAHFB5IBAzAuN5gBAKABAaABAqoBB2d3cy13aXrAAQE&sclient=psy-ab&ved=0ahUKEwiS7vqF3-XqAhUjAxAIHQSlDKwQ4dUDCAw&uact=5 https://github.com/awinogradov/xjst-ddsl https://github.com/awinogradov/ddsl-react

PavelZubkov commented 4 years ago
  1. Объявляем компонент, описываея его семантику, дефолтные компоненты и просы
  2. через Component.adequateExtend расширяем, переопределяем все что хотим (мб тут пригодятся идеи из аггрегаций, например глянь синтаксис монговских аггрегаций)
  3. Используем. Через пропсы можем переопределить также что угодно, но лучше не увлекаться. Для переопределения какой-нибудь мелочи должно быть удобно.
nin-jin commented 4 years ago

Интересный вариант. Но как это подружить со статической типизацией?

PavelZubkov commented 4 years ago

Интересный вариант. Но как это подружить со статической типизацией?

т.к. все действия в рантайме, то кмк никак(хотя я слаб в тс и типизации). Но можно динамическую - генерировать проптайпсы. Мб возможно генерировать типы по коду и как-нибудь подсовывать тс. Как в мол генерируется папка "-". Думаю лучше адекватное расширение без статической типизации, чем с типизацией но без расширения

Подсказки можно показывать через плагин в редакторе

PavelZubkov commented 4 years ago

https://github.com/elierotenberg/react-traverse

PavelZubkov commented 4 years ago

Какие действия по расширению/переопределению компонентов нужны?

Манипуляция реакт элементами

// Базовый компонент
// параметр dialog - прокси, обращаясь к которому мы описываем структуру компанента
// через пропс *as* мы указываем какие компоненты будут рендерится по умолчанию
@AdequateComponent
const Dialog = (props, dialog) => (
  <dialog as={Card}>
    <dialog.header as={Card.Header}>
      <dialog.title as={Text.Title}>Welcome</dialog.title>
    </dialog.header>
    <dialog.body as={Card.Body}>How are you?</dialog.body>
    <dialog.footer as={Card.Footer}>
      <dialog.button.bad as={Button}>Bad</dialog.button.bad>
      <dialog.button.ok as={Button}>Ok</dialog.button.ok>
    </dialog.footer>
  </dialog>
);

вставка:

const ExtendedDialog = Dialog.extend(
  <>
  // Добавление кнопки с иконкой для закрытия диалога
  // внешний тег <dialog.header> выступает в роли селектора, так мы указываем относительно какого компонента, производим изменение
  <dialog.header>
    <dialog.button.close as={Button} mode="APPEND">
      {/* режим - добавление в конец. Кнопка с иконкой добавится в конец dialog.header */}
      <dialog.icon.close as={Icon.Close} />
    </dialog.button.close>
  <dialog.header>
  <dialog.footer>
    {/* Удаление кнопки bad */}
    <dialog.button.bad mode="REMOVE" />
  </dialog.footer>
  </>
)

TODO: дописать все примеры

PavelZubkov commented 4 years ago

Есть предложение расширять в как во вью. Например, есть базовый компонент:

// Базовый компонент
// параметр dialog - прокси, обращаясь к которому мы описываем структуру компанента
// через пропс *as* мы указываем какие компоненты будут рендерится по умолчанию
@AdequateComponent
const Dialog = (props, dialog) => (
  <dialog as={Card}>
    <dialog.header as={Card.Header}>
      <dialog.title as={Text.Title}>Welcome</dialog.title>
    </dialog.header>
    <dialog.body as={Card.Body}>How are you?</dialog.body>
    <dialog.footer as={Card.Footer}>
      <dialog.button.bad as={Button}>Bad</dialog.button.bad>
      <dialog.button.ok as={Button}>Ok</dialog.button.ok>
    </dialog.footer>
  </dialog>
);

Нужен диалог с текстом "Недостаточно прав" и убрать кнопку cancel:


function DialogBadPermissions(props) {
  return (
    <Dialog.extend> // нужен какой-то ключ, что бы отличить children от расширения?
      <dialog.body>Недостаточно прав</dialog.body> // меняем боди
      <dialog.button.cancel as={null} /> // не рендерим кнопку
    </Dialog.extend>
  );
}
PavelZubkov commented 4 years ago

с пропсами идея в том, что бы их не хардкодить, а прокидывать куда нужно используя структуру(путь в структуре). В самом компоненте не юзать this.props Побочным действием получается, что можно и какой-нибудь компонент внутри переопределить, что для какого-нибудь единичного переопределения может быть удобным. Синтаксис расширения уже для того что бы переделывать структуру.